blob: 0bbd28242290cb786ac96b0036f02f1c5c110231 [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éraud91fd4b62023-08-29 20:50:01 -040091 std::lock_guard<std::mutex> lock(mappingMutex_);
92 mappingList_->clear();
93 mappingListUpdateTimer_.cancel();
94 controllerList_.clear();
95 protocolList_.clear();
96 shutdownComplete_ = true;
97 cv.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -040098}
99
100void
101UPnPContext::shutdown()
102{
103 std::unique_lock<std::mutex> lk(mappingMutex_);
104 std::condition_variable cv;
105
Adrien Béraud370257c2023-08-15 20:53:09 -0400106 ctx->post([&, this] { shutdown(cv); });
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500107
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400108 if (logger_) logger_->debug("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400109
110 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400111 if (logger_) logger_->debug("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400112 } else {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400113 if (logger_) logger_->error("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400114 }
François-Simon Fauteux-Chapleau648907c2024-02-06 15:16:48 -0500115 // NOTE: It's important to unlock mappingMutex_ here, otherwise we get a
116 // deadlock when the call to cv.wait_for() above times out before we return
117 // from proto->terminate() in shutdown(cv).
118 lk.unlock();
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400119
120 if (ioContextRunner_) {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500121 if (logger_) logger_->debug("UPnPContext: stopping io_context thread {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400122 ctx->stop();
123 ioContextRunner_->join();
124 ioContextRunner_.reset();
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500125 if (logger_) logger_->debug("UPnPContext: stopping io_context thread - finished {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400126 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400127}
128
129UPnPContext::~UPnPContext()
130{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400131 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400132}
133
134void
135UPnPContext::init()
136{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400137#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400138 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400139 natPmp->setObserver(this);
140 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
141#endif
142
143#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400144 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400145 pupnp->setObserver(this);
146 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
147#endif
148}
149
150void
151UPnPContext::startUpnp()
152{
153 assert(not controllerList_.empty());
154
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400155 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400156
157 // Request a new IGD search.
158 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400159 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400160 }
161
162 started_ = true;
163}
164
165void
166UPnPContext::stopUpnp(bool forceRelease)
167{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400168 if (logger_) logger_->debug("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400169
170 // Clear all current mappings if any.
171
172 // Use a temporary list to avoid processing the mapping
173 // list while holding the lock.
174 std::list<Mapping::sharedPtr_t> toRemoveList;
175 {
176 std::lock_guard<std::mutex> lock(mappingMutex_);
177
178 PortType types[2] {PortType::TCP, PortType::UDP};
179 for (auto& type : types) {
180 auto& mappingList = getMappingList(type);
181 for (auto const& [_, map] : mappingList) {
182 toRemoveList.emplace_back(map);
183 }
184 }
185 // Invalidate the current IGDs.
186 preferredIgd_.reset();
187 validIgdList_.clear();
188 }
189 for (auto const& map : toRemoveList) {
190 requestRemoveMapping(map);
191
Adrien Béraud370257c2023-08-15 20:53:09 -0400192 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400193 // shutting down (hence set it to false). NotifyCallback
194 // would trigger a new SIP registration and create a
195 // false registered state upon program close.
196 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400197 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400198 // We dont remove mappings with auto-update enabled,
199 // unless forceRelease is true.
200 if (not map->getAutoUpdate() or forceRelease) {
201 map->enableAutoUpdate(false);
202 unregisterMapping(map);
203 }
204 }
205
206 // Clear all current IGDs.
207 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400208 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400209 }
210
211 started_ = false;
212}
213
214uint16_t
215UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
216{
217 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
218 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
219
220 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400221 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400222 // Must be called with valid range.
223 assert(false);
224 }
225
226 int fact = mustBeEven ? 2 : 1;
227 if (mustBeEven) {
228 minPort /= fact;
229 maxPort /= fact;
230 }
231
232 // Seed the generator.
233 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
234 // Define the range.
235 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
236 return dist(gen) * fact;
237}
238
239void
240UPnPContext::connectivityChanged()
241{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400242 // Debounce the connectivity change notification.
243 connectivityChangedTimer_.expires_after(std::chrono::milliseconds(50));
244 connectivityChangedTimer_.async_wait(std::bind(&UPnPContext::_connectivityChanged, this, std::placeholders::_1));
245}
246
247void
248UPnPContext::_connectivityChanged(const asio::error_code& ec)
249{
250 if (ec == asio::error::operation_aborted)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400251 return;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400252
253 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
254
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400255 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400256
257 auto restartUpnp = false;
258
259 // On reception of "connectivity change" notification, the UPNP search
260 // will be restarted if either there is no valid IGD, or the IGD address
261 // changed.
262
263 if (not isReady()) {
264 restartUpnp = true;
265 } else {
266 // Check if the host address changed.
267 for (auto const& [_, protocol] : protocolList_) {
268 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400269 if (logger_) logger_->warn("Host address changed from {} to {}",
270 protocol->getHostAddress().toString(),
271 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400272 protocol->clearIgds();
273 restartUpnp = true;
274 break;
275 }
276 }
277 }
278
279 // We have at least one valid IGD and the host address did
280 // not change, so no need to restart.
281 if (not restartUpnp) {
282 return;
283 }
284
285 // No registered controller. A new search will be performed when
286 // a controller is registered.
287 if (controllerList_.empty())
288 return;
289
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400290 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400291
292 stopUpnp();
293 startUpnp();
294
295 // Mapping with auto update enabled must be processed first.
296 processMappingWithAutoUpdate();
297}
298
299void
300UPnPContext::setPublicAddress(const IpAddr& addr)
301{
302 if (not addr)
303 return;
304
305 std::lock_guard<std::mutex> lock(mappingMutex_);
306 if (knownPublicAddress_ != addr) {
307 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400308 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400309 }
310}
311
312bool
313UPnPContext::isReady() const
314{
315 std::lock_guard<std::mutex> lock(mappingMutex_);
316 return not validIgdList_.empty();
317}
318
319IpAddr
320UPnPContext::getExternalIP() const
321{
322 std::lock_guard<std::mutex> lock(mappingMutex_);
323 // Return the first IGD Ip available.
324 if (not validIgdList_.empty()) {
325 return (*validIgdList_.begin())->getPublicIp();
326 }
327 return {};
328}
329
330Mapping::sharedPtr_t
331UPnPContext::reserveMapping(Mapping& requestedMap)
332{
333 auto desiredPort = requestedMap.getExternalPort();
334
335 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400336 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
337 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400338 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400339 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400340 }
341
342 Mapping::sharedPtr_t mapRes;
343
344 {
345 std::lock_guard<std::mutex> lock(mappingMutex_);
346 auto& mappingList = getMappingList(requestedMap.getType());
347
348 // We try to provide a mapping in "OPEN" state. If not found,
349 // we provide any available mapping. In this case, it's up to
350 // the caller to use it or not.
351 for (auto const& [_, map] : mappingList) {
352 // If the desired port is null, we pick the first available port.
353 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
354 and map->isAvailable()) {
355 // Considere the first available mapping regardless of its
356 // state. A mapping with OPEN state will be used if found.
357 if (not mapRes)
358 mapRes = map;
359
360 if (map->getState() == MappingState::OPEN) {
361 // Found an "OPEN" mapping. We are done.
362 mapRes = map;
363 break;
364 }
365 }
366 }
367 }
368
369 // Create a mapping if none was available.
370 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400371 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400372 mapRes = registerMapping(requestedMap);
373 }
374
375 if (mapRes) {
376 // Make the mapping unavailable
377 mapRes->setAvailable(false);
378 // Copy attributes.
379 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
380 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
381 // Notify the listener.
382 if (auto cb = mapRes->getNotifyCallback())
383 cb(mapRes);
384 }
385
386 updateMappingList(true);
387
388 return mapRes;
389}
390
391void
392UPnPContext::releaseMapping(const Mapping& map)
393{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400394 ctx->dispatch([this, map] {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500395 if (shutdownComplete_)
396 return;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400397 auto mapPtr = getMappingWithKey(map.getMapKey());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400398
Adrien Béraudc36965c2023-08-17 21:50:27 -0400399 if (not mapPtr) {
400 // Might happen if the mapping failed or was never granted.
401 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
402 return;
403 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400404
Adrien Béraudc36965c2023-08-17 21:50:27 -0400405 if (mapPtr->isAvailable()) {
406 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
407 return;
408 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400409
Adrien Béraudc36965c2023-08-17 21:50:27 -0400410 // Remove it.
411 requestRemoveMapping(mapPtr);
412 unregisterMapping(mapPtr);
413 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414}
415
416void
417UPnPContext::registerController(void* controller)
418{
419 {
420 std::lock_guard<std::mutex> lock(mappingMutex_);
421 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400422 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400423 return;
424 }
Adrien Béraudc36965c2023-08-17 21:50:27 -0400425 auto ret = controllerList_.emplace(controller);
426 if (not ret.second) {
427 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
428 return;
429 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400430 }
431
Adrien Berauda8731ac2023-08-17 12:19:39 -0400432 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400433 if (not started_)
434 startUpnp();
435}
436
437void
438UPnPContext::unregisterController(void* controller)
439{
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500440 if (shutdownComplete_)
441 return;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400442 std::unique_lock<std::mutex> lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400443 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400444 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400445 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400446 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400447 }
448
449 if (controllerList_.empty()) {
Adrien Béraudc36965c2023-08-17 21:50:27 -0400450 lock.unlock();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400451 stopUpnp();
452 }
453}
454
455uint16_t
456UPnPContext::getAvailablePortNumber(PortType type)
457{
458 // Only return an availalable random port. No actual
459 // reservation is made here.
460
461 std::lock_guard<std::mutex> lock(mappingMutex_);
462 auto& mappingList = getMappingList(type);
463 int tryCount = 0;
464 while (tryCount++ < MAX_REQUEST_RETRIES) {
465 uint16_t port = generateRandomPort(type);
466 Mapping map(type, port, port);
467 if (mappingList.find(map.getMapKey()) == mappingList.end())
468 return port;
469 }
470
471 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400472 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400473 return 0;
474}
475
476void
477UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
478{
479 assert(map);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400480 auto const& igd = getPreferredIgd();
481 // We must have at least a valid IGD pointer if we get here.
482 // Not this method is called only if there were a valid IGD, however,
483 // because the processing is asynchronous, it's possible that the IGD
484 // was invalidated when the this code executed.
485 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400486 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400487 return;
488 }
489
490 map->setIgd(igd);
491
Adrien Berauda8731ac2023-08-17 12:19:39 -0400492 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
493 map->toString(),
494 igd->getProtocolName(),
495 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400496
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400497 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400498
499 auto const& protocol = protocolList_.at(igd->getProtocol());
500 protocol->requestMappingAdd(*map);
501}
502
503bool
504UPnPContext::provisionNewMappings(PortType type, int portCount)
505{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400506 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400507
508 assert(portCount > 0);
509
510 while (portCount > 0) {
511 auto port = getAvailablePortNumber(type);
512 if (port > 0) {
513 // Found an available port number
514 portCount--;
515 Mapping map(type, port, port, true);
516 registerMapping(map);
517 } else {
518 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400519 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400520 return false;
521 }
522 }
523
524 return true;
525}
526
527bool
528UPnPContext::deleteUnneededMappings(PortType type, int portCount)
529{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400530 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400531
532 assert(portCount > 0);
533
Adrien Béraud370257c2023-08-15 20:53:09 -0400534 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400535
536 std::lock_guard<std::mutex> lock(mappingMutex_);
537 auto& mappingList = getMappingList(type);
538
539 for (auto it = mappingList.begin(); it != mappingList.end();) {
540 auto map = it->second;
541 assert(map);
542
543 if (not map->isAvailable()) {
544 it++;
545 continue;
546 }
547
548 if (map->getState() == MappingState::OPEN and portCount > 0) {
549 // Close portCount mappings in "OPEN" state.
550 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400551 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400552 portCount--;
553 } else if (map->getState() != MappingState::OPEN) {
554 // If this methods is called, it means there are more open
555 // mappings than required. So, all mappings in a state other
556 // than "OPEN" state (typically in in-progress state) will
557 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400558 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400559 } else {
560 it++;
561 }
562 }
563
564 return true;
565}
566
567void
568UPnPContext::updatePreferredIgd()
569{
Adrien Béraud370257c2023-08-15 20:53:09 -0400570 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400571
572 if (preferredIgd_ and preferredIgd_->isValid())
573 return;
574
575 // Reset and search for the best IGD.
576 preferredIgd_.reset();
577
578 for (auto const& [_, protocol] : protocolList_) {
579 if (protocol->isReady()) {
580 auto igdList = protocol->getIgdList();
581 assert(not igdList.empty());
582 auto const& igd = igdList.front();
583 if (not igd->isValid())
584 continue;
585
586 // Prefer NAT-PMP over PUPNP.
587 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
588 continue;
589
590 // Update.
591 preferredIgd_ = igd;
592 }
593 }
594
595 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400596 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
597 preferredIgd_->getProtocolName(),
598 preferredIgd_->getUID(),
599 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400600 }
601}
602
603std::shared_ptr<IGD>
604UPnPContext::getPreferredIgd() const
605{
Adrien Béraud370257c2023-08-15 20:53:09 -0400606 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400607
608 return preferredIgd_;
609}
610
611void
612UPnPContext::updateMappingList(bool async)
613{
614 // Run async if requested.
615 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400616 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400617 return;
618 }
619
Adrien Béraud370257c2023-08-15 20:53:09 -0400620 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400621
622 // Update the preferred IGD.
623 updatePreferredIgd();
624
Adrien Béraud25c30c42023-07-05 13:46:54 -0400625 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400626
627 // Skip if no controller registered.
628 if (controllerList_.empty())
629 return;
630
631 // Cancel the current timer (if any) and re-schedule.
632 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
633 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400634 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400635 // No valid IGD. Nothing to do.
636 return;
637 }
638
Adrien Berauda8731ac2023-08-17 12:19:39 -0400639 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400640 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
641 if (ec != asio::error::operation_aborted)
642 updateMappingList(false);
643 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400644
645 // Process pending requests if any.
646 processPendingRequests(prefIgd);
647
648 // Make new requests for mappings that failed and have
649 // the auto-update option enabled.
650 processMappingWithAutoUpdate();
651
652 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
653
654 for (auto idx : {0, 1}) {
655 auto type = typeArray[idx];
656
657 MappingStatus status;
658 getMappingStatus(type, status);
659
Adrien Berauda8731ac2023-08-17 12:19:39 -0400660 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
661 "in-progress, {:d} failed",
662 Mapping::getTypeStr(type),
663 status.sum(),
664 status.openCount_,
665 status.readyCount_,
666 status.openCount_ - status.readyCount_,
667 status.pendingCount_,
668 status.inProgressCount_,
669 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400670
671 if (status.failedCount_ > 0) {
672 std::lock_guard<std::mutex> lock(mappingMutex_);
673 auto const& mappingList = getMappingList(type);
674 for (auto const& [_, map] : mappingList) {
675 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400676 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
677 map->toString(true),
678 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400679 }
680 }
681 }
682
683 int toRequestCount = (int) minOpenPortLimit_[idx]
684 - (int) (status.readyCount_ + status.inProgressCount_
685 + status.pendingCount_);
686
687 // Provision/release mappings accordingly.
688 if (toRequestCount > 0) {
689 // Take into account the request in-progress when making
690 // requests for new mappings.
691 provisionNewMappings(type, toRequestCount);
692 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
693 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
694 }
695 }
696
697 // Prune the mapping list if needed
698 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
699#if HAVE_LIBNATPMP
700 // Dont perform if NAT-PMP is valid.
701 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
702#endif
703 {
704 pruneMappingList();
705 }
706 }
707
708#if HAVE_LIBNATPMP
709 // Renew nat-pmp allocations
710 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
711 renewAllocations();
712#endif
713}
714
715void
716UPnPContext::pruneMappingList()
717{
Adrien Béraud370257c2023-08-15 20:53:09 -0400718 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400719
720 MappingStatus status;
721 getMappingStatus(status);
722
723 // Do not prune the list if there are pending/in-progress requests.
724 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
725 return;
726 }
727
728 auto const& igd = getPreferredIgd();
729 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
730 return;
731 }
732 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
733
734 auto remoteMapList = protocol->getMappingsListByDescr(igd,
735 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Berauda0683d12023-08-22 18:09:02 -0400736 /*if (remoteMapList.empty()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400737 std::lock_guard<std::mutex> lock(mappingMutex_);
738 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400739 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400740 }
Adrien Berauda0683d12023-08-22 18:09:02 -0400741 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400742
743 pruneUnMatchedMappings(igd, remoteMapList);
744 pruneUnTrackedMappings(igd, remoteMapList);
745}
746
747void
748UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
749 const std::map<Mapping::key_t, Mapping>& remoteMapList)
750{
751 // Check/synchronize local mapping list with the list
752 // returned by the IGD.
753
Adrien Berauda0683d12023-08-22 18:09:02 -0400754 for (auto type: {PortType::TCP, PortType::UDP}) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400755 // Use a temporary list to avoid processing mappings while holding the lock.
756 std::list<Mapping::sharedPtr_t> toRemoveList;
757 {
758 std::lock_guard<std::mutex> lock(mappingMutex_);
Adrien Berauda0683d12023-08-22 18:09:02 -0400759 for (auto const& [_, map] : getMappingList(type)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400760 // Only check mappings allocated by UPNP protocol.
761 if (map->getProtocol() != NatProtocolType::PUPNP) {
762 continue;
763 }
764 // Set mapping as failed if not found in the list
765 // returned by the IGD.
766 if (map->getState() == MappingState::OPEN
767 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
768 toRemoveList.emplace_back(map);
769
Adrien Berauda8731ac2023-08-17 12:19:39 -0400770 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
771 "remote list. Mark as failed!",
772 map->toString(),
773 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400774 }
775 }
776 }
777
778 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400779 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400780 unregisterMapping(map);
781 }
782 }
783}
784
785void
786UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
787 const std::map<Mapping::key_t, Mapping>& remoteMapList)
788{
789 // Use a temporary list to avoid processing mappings while holding the lock.
790 std::list<Mapping> toRemoveList;
791 {
792 std::lock_guard<std::mutex> lock(mappingMutex_);
793
794 for (auto const& [_, map] : remoteMapList) {
795 // Must has valid IGD pointer and use UPNP protocol.
796 assert(map.getIgd());
797 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
798 auto& mappingList = getMappingList(map.getType());
799 auto it = mappingList.find(map.getMapKey());
800 if (it == mappingList.end()) {
801 // Not present, request mapping remove.
802 toRemoveList.emplace_back(std::move(map));
803 // Make only few remove requests at once.
804 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
805 break;
806 }
807 }
808 }
809
810 // Remove un-tracked mappings.
811 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
812 for (auto const& map : toRemoveList) {
813 protocol->requestMappingRemove(map);
814 }
815}
816
817void
818UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
819{
Adrien Béraud370257c2023-08-15 20:53:09 -0400820 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400821
822 // Use temporary list to avoid holding the lock while
823 // processing the mapping list.
824 std::list<Mapping::sharedPtr_t> toRemoveList;
825 {
826 std::lock_guard<std::mutex> lock(mappingMutex_);
827
828 PortType types[2] {PortType::TCP, PortType::UDP};
829 for (auto& type : types) {
830 auto& mappingList = getMappingList(type);
831 for (auto const& [_, map] : mappingList) {
832 if (map->getIgd() == igd)
833 toRemoveList.emplace_back(map);
834 }
835 }
836 }
837
838 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400839 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
840 map->toString(),
841 igd->toString(),
842 igd->getProtocolName());
Adrien Béraud56740312023-08-23 08:38:28 -0400843 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400844 unregisterMapping(map);
845 }
846}
847
848void
849UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
850{
851 // This list holds the mappings to be requested. This is
852 // needed to avoid performing the requests while holding
853 // the lock.
854 std::list<Mapping::sharedPtr_t> requestsList;
855
856 // Populate the list of requests to perform.
857 {
858 std::lock_guard<std::mutex> lock(mappingMutex_);
859 PortType typeArray[2] {PortType::TCP, PortType::UDP};
860
861 for (auto type : typeArray) {
862 auto& mappingList = getMappingList(type);
863 for (auto& [_, map] : mappingList) {
864 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400865 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
866 map->toString(),
867 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400868 requestsList.emplace_back(map);
869 }
870 }
871 }
872 }
873
874 // Process the pending requests.
875 for (auto const& map : requestsList) {
876 requestMapping(map);
877 }
878}
879
880void
881UPnPContext::processMappingWithAutoUpdate()
882{
883 // This list holds the mappings to be requested. This is
884 // needed to avoid performing the requests while holding
885 // the lock.
886 std::list<Mapping::sharedPtr_t> requestsList;
887
888 // Populate the list of requests for mappings with auto-update enabled.
889 {
890 std::lock_guard<std::mutex> lock(mappingMutex_);
891 PortType typeArray[2] {PortType::TCP, PortType::UDP};
892
893 for (auto type : typeArray) {
894 auto& mappingList = getMappingList(type);
895 for (auto const& [_, map] : mappingList) {
896 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
897 requestsList.emplace_back(map);
898 }
899 }
900 }
901 }
902
903 for (auto const& oldMap : requestsList) {
904 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400905 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
906 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400907
908 // Reserve a new mapping.
909 Mapping newMapping(oldMap->getType());
910 newMapping.enableAutoUpdate(true);
911 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
912
913 auto const& mapPtr = reserveMapping(newMapping);
914 assert(mapPtr);
915
916 // Release the old one.
917 oldMap->setAvailable(true);
918 oldMap->enableAutoUpdate(false);
919 oldMap->setNotifyCallback(nullptr);
920 unregisterMapping(oldMap);
921 }
922}
923
924void
925UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
926{
927 assert(igd);
928
Adrien Béraud612b55b2023-05-29 10:42:04 -0400929 // Reset to start search for a new best IGD.
930 preferredIgd_.reset();
931
932 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
933 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
934 : "INVALID";
935
936 auto const& igdLocalAddr = igd->getLocalIp();
937 auto protocolName = igd->getProtocolName();
938
Adrien Berauda8731ac2023-08-17 12:19:39 -0400939 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
940 igd->getUID(),
941 igd->toString(),
942 protocolName,
943 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400944
945 // Check if the IGD has valid addresses.
946 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400947 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400948 return;
949 }
950
951 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400952 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400953 return;
954 }
955
956 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400957 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
958 " The mapped addresses might not be reachable",
959 protocolName,
960 igd->getPublicIp().toString(),
961 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400962 }
963
964 // The IGD was removed or is invalid.
965 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400966 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
967 igd->getUID(),
968 igd->toString(),
969 protocolName,
970 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400971
972 pruneMappingsWithInvalidIgds(igd);
973
974 std::lock_guard<std::mutex> lock(mappingMutex_);
975 validIgdList_.erase(igd);
976 return;
977 }
978
979 // Update the IGD list.
980 {
981 std::lock_guard<std::mutex> lock(mappingMutex_);
982 auto ret = validIgdList_.emplace(igd);
983 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400984 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
985 protocolName,
986 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400987 } else {
988 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400989 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
990 protocolName,
991 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400992 return;
993 }
994 }
995
996 // Update the provisionned mappings.
997 updateMappingList(false);
998}
999
1000void
1001UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1002{
Adrien Béraud370257c2023-08-15 20:53:09 -04001003 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001004
1005 // Check if we have a pending request for this response.
1006 auto map = getMappingWithKey(mapRes.getMapKey());
1007 if (not map) {
1008 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001009 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1010 mapRes.toString(),
1011 igd->toString(),
1012 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001013 return;
1014 }
1015
1016 // The mapping request is new and successful. Update.
1017 map->setIgd(igd);
1018 map->setInternalAddress(mapRes.getInternalAddress());
1019 map->setExternalPort(mapRes.getExternalPort());
1020
1021 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001022 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001023
Adrien Berauda8731ac2023-08-17 12:19:39 -04001024 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1025 map->toString(),
1026 igd->toString(),
1027 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001028
1029 // Call setValid() to reset the errors counter. We need
1030 // to reset the counter on each successful response.
1031 igd->setValid(true);
1032}
1033
1034#if HAVE_LIBNATPMP
1035void
1036UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1037{
1038 auto mapPtr = getMappingWithKey(map.getMapKey());
1039
1040 if (not mapPtr) {
1041 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001042 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1043 map.toString(),
1044 igd->toString(),
1045 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001046 return;
1047 }
1048 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1049 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001050 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1051 mapPtr->toString(),
1052 igd->toString(),
1053 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001054 return;
1055 }
1056
1057 mapPtr->setRenewalTime(map.getRenewalTime());
1058}
1059#endif
1060
1061void
1062UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1063{
Adrien Béraud370257c2023-08-15 20:53:09 -04001064 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001065 // Silently ignore if the mapping is invalid
1066 return;
1067 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001068 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1069 protocol->requestMappingRemove(*map);
1070}
1071
1072void
1073UPnPContext::deleteAllMappings(PortType type)
1074{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001075 std::lock_guard<std::mutex> lock(mappingMutex_);
1076 auto& mappingList = getMappingList(type);
1077
1078 for (auto const& [_, map] : mappingList) {
1079 requestRemoveMapping(map);
1080 }
1081}
1082
1083void
1084UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1085{
1086 if (not mapRes.isValid())
1087 return;
1088
Adrien Béraud612b55b2023-05-29 10:42:04 -04001089 auto map = getMappingWithKey(mapRes.getMapKey());
1090 // Notify the listener.
1091 if (map and map->getNotifyCallback())
1092 map->getNotifyCallback()(map);
1093}
1094
1095Mapping::sharedPtr_t
1096UPnPContext::registerMapping(Mapping& map)
1097{
1098 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001099 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001100 auto port = getAvailablePortNumber(map.getType());
1101 map.setExternalPort(port);
1102 map.setInternalPort(port);
1103 }
1104
1105 // Newly added mapping must be in pending state by default.
1106 map.setState(MappingState::PENDING);
1107
1108 Mapping::sharedPtr_t mapPtr;
1109
1110 {
1111 std::lock_guard<std::mutex> lock(mappingMutex_);
1112 auto& mappingList = getMappingList(map.getType());
1113
1114 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1115 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001116 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001117 return {};
1118 }
1119 mapPtr = ret.first->second;
1120 assert(mapPtr);
1121 }
1122
1123 // No available IGD. The pending mapping requests will be processed
1124 // when a IGD becomes available (in onIgdAdded() method).
1125 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001126 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001127 } else {
1128 requestMapping(mapPtr);
1129 }
1130
1131 return mapPtr;
1132}
1133
Adrien Béraud612b55b2023-05-29 10:42:04 -04001134void
1135UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1136{
Adrien Béraud370257c2023-08-15 20:53:09 -04001137 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001138
1139 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001140 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001141 return;
1142 }
1143
1144 if (map->getAutoUpdate()) {
1145 // Dont unregister mappings with auto-update enabled.
1146 return;
1147 }
1148 auto& mappingList = getMappingList(map->getType());
1149
1150 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001151 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001152 } else {
1153 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001154 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1155 map->toString(),
1156 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001157 }
1158}
1159
1160std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1161UPnPContext::getMappingList(PortType type)
1162{
1163 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1164 return mappingList_[typeIdx];
1165}
1166
1167Mapping::sharedPtr_t
1168UPnPContext::getMappingWithKey(Mapping::key_t key)
1169{
1170 std::lock_guard<std::mutex> lock(mappingMutex_);
1171 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1172 auto it = mappingList.find(key);
1173 if (it == mappingList.end())
1174 return nullptr;
1175 return it->second;
1176}
1177
1178void
1179UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1180{
1181 std::lock_guard<std::mutex> lock(mappingMutex_);
1182 auto& mappingList = getMappingList(type);
1183
1184 for (auto const& [_, map] : mappingList) {
1185 switch (map->getState()) {
1186 case MappingState::PENDING: {
1187 status.pendingCount_++;
1188 break;
1189 }
1190 case MappingState::IN_PROGRESS: {
1191 status.inProgressCount_++;
1192 break;
1193 }
1194 case MappingState::FAILED: {
1195 status.failedCount_++;
1196 break;
1197 }
1198 case MappingState::OPEN: {
1199 status.openCount_++;
1200 if (map->isAvailable())
1201 status.readyCount_++;
1202 break;
1203 }
1204
1205 default:
1206 // Must not get here.
1207 assert(false);
1208 break;
1209 }
1210 }
1211}
1212
1213void
1214UPnPContext::getMappingStatus(MappingStatus& status)
1215{
1216 getMappingStatus(PortType::TCP, status);
1217 getMappingStatus(PortType::UDP, status);
1218}
1219
1220void
1221UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1222{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001223 auto const& map = getMappingWithKey(mapRes.getMapKey());
1224 if (not map) {
1225 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001226 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1227 mapRes.toString(),
1228 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001229 return;
1230 }
1231
1232 auto igd = map->getIgd();
1233 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001234 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001235 return;
1236 }
1237
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001238 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001239 unregisterMapping(map);
1240
Adrien Berauda8731ac2023-08-17 12:19:39 -04001241 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1242 map->toString(),
1243 igd->toString(),
1244 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001245}
1246
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001247void
1248UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1249{
1250 // CHECK_VALID_THREAD();
1251
1252 assert(map);
1253
1254 // Ignore if the state did not change.
1255 if (newState == map->getState()) {
1256 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1257 return;
1258 }
1259
1260 // Update the state.
1261 map->setState(newState);
1262
1263 // Notify the listener if set.
1264 if (notify and map->getNotifyCallback())
1265 map->getNotifyCallback()(map);
1266}
1267
Adrien Béraud612b55b2023-05-29 10:42:04 -04001268#if HAVE_LIBNATPMP
1269void
1270UPnPContext::renewAllocations()
1271{
Adrien Béraud370257c2023-08-15 20:53:09 -04001272 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001273
1274 // Check if the we have valid PMP IGD.
1275 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1276
1277 auto now = sys_clock::now();
1278 std::vector<Mapping::sharedPtr_t> toRenew;
1279
1280 for (auto type : {PortType::TCP, PortType::UDP}) {
1281 std::lock_guard<std::mutex> lock(mappingMutex_);
1282 auto mappingList = getMappingList(type);
1283 for (auto const& [_, map] : mappingList) {
1284 if (not map->isValid())
1285 continue;
1286 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1287 continue;
1288 if (map->getState() != MappingState::OPEN)
1289 continue;
1290 if (now < map->getRenewalTime())
1291 continue;
1292
1293 toRenew.emplace_back(map);
1294 }
1295 }
1296
1297 // Quit if there are no mapping to renew
1298 if (toRenew.empty())
1299 return;
1300
1301 for (auto const& map : toRenew) {
1302 pmpProto->requestMappingRenew(*map);
1303 }
1304}
1305#endif
1306
1307} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001308} // namespace dhtnet