blob: 6a7273e571ab77fde1324a45ec0c7948170beae2 [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 }
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400115
116 if (ioContextRunner_) {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500117 if (logger_) logger_->debug("UPnPContext: stopping io_context thread {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400118 ctx->stop();
119 ioContextRunner_->join();
120 ioContextRunner_.reset();
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500121 if (logger_) logger_->debug("UPnPContext: stopping io_context thread - finished {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400122 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400123}
124
125UPnPContext::~UPnPContext()
126{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400127 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400128}
129
130void
131UPnPContext::init()
132{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400133#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400134 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400135 natPmp->setObserver(this);
136 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
137#endif
138
139#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400140 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400141 pupnp->setObserver(this);
142 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
143#endif
144}
145
146void
147UPnPContext::startUpnp()
148{
149 assert(not controllerList_.empty());
150
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400151 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152
153 // Request a new IGD search.
154 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400155 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400156 }
157
158 started_ = true;
159}
160
161void
162UPnPContext::stopUpnp(bool forceRelease)
163{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400164 if (logger_) logger_->debug("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400165
166 // Clear all current mappings if any.
167
168 // Use a temporary list to avoid processing the mapping
169 // list while holding the lock.
170 std::list<Mapping::sharedPtr_t> toRemoveList;
171 {
172 std::lock_guard<std::mutex> lock(mappingMutex_);
173
174 PortType types[2] {PortType::TCP, PortType::UDP};
175 for (auto& type : types) {
176 auto& mappingList = getMappingList(type);
177 for (auto const& [_, map] : mappingList) {
178 toRemoveList.emplace_back(map);
179 }
180 }
181 // Invalidate the current IGDs.
182 preferredIgd_.reset();
183 validIgdList_.clear();
184 }
185 for (auto const& map : toRemoveList) {
186 requestRemoveMapping(map);
187
Adrien Béraud370257c2023-08-15 20:53:09 -0400188 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400189 // shutting down (hence set it to false). NotifyCallback
190 // would trigger a new SIP registration and create a
191 // false registered state upon program close.
192 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400193 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400194 // We dont remove mappings with auto-update enabled,
195 // unless forceRelease is true.
196 if (not map->getAutoUpdate() or forceRelease) {
197 map->enableAutoUpdate(false);
198 unregisterMapping(map);
199 }
200 }
201
202 // Clear all current IGDs.
203 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400204 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400205 }
206
207 started_ = false;
208}
209
210uint16_t
211UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
212{
213 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
214 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
215
216 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400217 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400218 // Must be called with valid range.
219 assert(false);
220 }
221
222 int fact = mustBeEven ? 2 : 1;
223 if (mustBeEven) {
224 minPort /= fact;
225 maxPort /= fact;
226 }
227
228 // Seed the generator.
229 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
230 // Define the range.
231 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
232 return dist(gen) * fact;
233}
234
235void
236UPnPContext::connectivityChanged()
237{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400238 // Debounce the connectivity change notification.
239 connectivityChangedTimer_.expires_after(std::chrono::milliseconds(50));
240 connectivityChangedTimer_.async_wait(std::bind(&UPnPContext::_connectivityChanged, this, std::placeholders::_1));
241}
242
243void
244UPnPContext::_connectivityChanged(const asio::error_code& ec)
245{
246 if (ec == asio::error::operation_aborted)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400247 return;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400248
249 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
250
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400251 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400252
253 auto restartUpnp = false;
254
255 // On reception of "connectivity change" notification, the UPNP search
256 // will be restarted if either there is no valid IGD, or the IGD address
257 // changed.
258
259 if (not isReady()) {
260 restartUpnp = true;
261 } else {
262 // Check if the host address changed.
263 for (auto const& [_, protocol] : protocolList_) {
264 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400265 if (logger_) logger_->warn("Host address changed from {} to {}",
266 protocol->getHostAddress().toString(),
267 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400268 protocol->clearIgds();
269 restartUpnp = true;
270 break;
271 }
272 }
273 }
274
275 // We have at least one valid IGD and the host address did
276 // not change, so no need to restart.
277 if (not restartUpnp) {
278 return;
279 }
280
281 // No registered controller. A new search will be performed when
282 // a controller is registered.
283 if (controllerList_.empty())
284 return;
285
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400286 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400287
288 stopUpnp();
289 startUpnp();
290
291 // Mapping with auto update enabled must be processed first.
292 processMappingWithAutoUpdate();
293}
294
295void
296UPnPContext::setPublicAddress(const IpAddr& addr)
297{
298 if (not addr)
299 return;
300
301 std::lock_guard<std::mutex> lock(mappingMutex_);
302 if (knownPublicAddress_ != addr) {
303 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400304 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400305 }
306}
307
308bool
309UPnPContext::isReady() const
310{
311 std::lock_guard<std::mutex> lock(mappingMutex_);
312 return not validIgdList_.empty();
313}
314
315IpAddr
316UPnPContext::getExternalIP() const
317{
318 std::lock_guard<std::mutex> lock(mappingMutex_);
319 // Return the first IGD Ip available.
320 if (not validIgdList_.empty()) {
321 return (*validIgdList_.begin())->getPublicIp();
322 }
323 return {};
324}
325
326Mapping::sharedPtr_t
327UPnPContext::reserveMapping(Mapping& requestedMap)
328{
329 auto desiredPort = requestedMap.getExternalPort();
330
331 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400332 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
333 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400334 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400335 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400336 }
337
338 Mapping::sharedPtr_t mapRes;
339
340 {
341 std::lock_guard<std::mutex> lock(mappingMutex_);
342 auto& mappingList = getMappingList(requestedMap.getType());
343
344 // We try to provide a mapping in "OPEN" state. If not found,
345 // we provide any available mapping. In this case, it's up to
346 // the caller to use it or not.
347 for (auto const& [_, map] : mappingList) {
348 // If the desired port is null, we pick the first available port.
349 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
350 and map->isAvailable()) {
351 // Considere the first available mapping regardless of its
352 // state. A mapping with OPEN state will be used if found.
353 if (not mapRes)
354 mapRes = map;
355
356 if (map->getState() == MappingState::OPEN) {
357 // Found an "OPEN" mapping. We are done.
358 mapRes = map;
359 break;
360 }
361 }
362 }
363 }
364
365 // Create a mapping if none was available.
366 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400367 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400368 mapRes = registerMapping(requestedMap);
369 }
370
371 if (mapRes) {
372 // Make the mapping unavailable
373 mapRes->setAvailable(false);
374 // Copy attributes.
375 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
376 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
377 // Notify the listener.
378 if (auto cb = mapRes->getNotifyCallback())
379 cb(mapRes);
380 }
381
382 updateMappingList(true);
383
384 return mapRes;
385}
386
387void
388UPnPContext::releaseMapping(const Mapping& map)
389{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400390 ctx->dispatch([this, map] {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500391 if (shutdownComplete_)
392 return;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400393 auto mapPtr = getMappingWithKey(map.getMapKey());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400394
Adrien Béraudc36965c2023-08-17 21:50:27 -0400395 if (not mapPtr) {
396 // Might happen if the mapping failed or was never granted.
397 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
398 return;
399 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400400
Adrien Béraudc36965c2023-08-17 21:50:27 -0400401 if (mapPtr->isAvailable()) {
402 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
403 return;
404 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400405
Adrien Béraudc36965c2023-08-17 21:50:27 -0400406 // Remove it.
407 requestRemoveMapping(mapPtr);
408 unregisterMapping(mapPtr);
409 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400410}
411
412void
413UPnPContext::registerController(void* controller)
414{
415 {
416 std::lock_guard<std::mutex> lock(mappingMutex_);
417 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400418 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400419 return;
420 }
Adrien Béraudc36965c2023-08-17 21:50:27 -0400421 auto ret = controllerList_.emplace(controller);
422 if (not ret.second) {
423 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
424 return;
425 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400426 }
427
Adrien Berauda8731ac2023-08-17 12:19:39 -0400428 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400429 if (not started_)
430 startUpnp();
431}
432
433void
434UPnPContext::unregisterController(void* controller)
435{
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500436 if (shutdownComplete_)
437 return;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400438 std::unique_lock<std::mutex> lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400439 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400440 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400441 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400442 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400443 }
444
445 if (controllerList_.empty()) {
Adrien Béraudc36965c2023-08-17 21:50:27 -0400446 lock.unlock();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400447 stopUpnp();
448 }
449}
450
451uint16_t
452UPnPContext::getAvailablePortNumber(PortType type)
453{
454 // Only return an availalable random port. No actual
455 // reservation is made here.
456
457 std::lock_guard<std::mutex> lock(mappingMutex_);
458 auto& mappingList = getMappingList(type);
459 int tryCount = 0;
460 while (tryCount++ < MAX_REQUEST_RETRIES) {
461 uint16_t port = generateRandomPort(type);
462 Mapping map(type, port, port);
463 if (mappingList.find(map.getMapKey()) == mappingList.end())
464 return port;
465 }
466
467 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400468 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400469 return 0;
470}
471
472void
473UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
474{
475 assert(map);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400476 auto const& igd = getPreferredIgd();
477 // We must have at least a valid IGD pointer if we get here.
478 // Not this method is called only if there were a valid IGD, however,
479 // because the processing is asynchronous, it's possible that the IGD
480 // was invalidated when the this code executed.
481 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400482 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400483 return;
484 }
485
486 map->setIgd(igd);
487
Adrien Berauda8731ac2023-08-17 12:19:39 -0400488 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
489 map->toString(),
490 igd->getProtocolName(),
491 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400492
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400493 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400494
495 auto const& protocol = protocolList_.at(igd->getProtocol());
496 protocol->requestMappingAdd(*map);
497}
498
499bool
500UPnPContext::provisionNewMappings(PortType type, int portCount)
501{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400502 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400503
504 assert(portCount > 0);
505
506 while (portCount > 0) {
507 auto port = getAvailablePortNumber(type);
508 if (port > 0) {
509 // Found an available port number
510 portCount--;
511 Mapping map(type, port, port, true);
512 registerMapping(map);
513 } else {
514 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400515 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400516 return false;
517 }
518 }
519
520 return true;
521}
522
523bool
524UPnPContext::deleteUnneededMappings(PortType type, int portCount)
525{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400526 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400527
528 assert(portCount > 0);
529
Adrien Béraud370257c2023-08-15 20:53:09 -0400530 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400531
532 std::lock_guard<std::mutex> lock(mappingMutex_);
533 auto& mappingList = getMappingList(type);
534
535 for (auto it = mappingList.begin(); it != mappingList.end();) {
536 auto map = it->second;
537 assert(map);
538
539 if (not map->isAvailable()) {
540 it++;
541 continue;
542 }
543
544 if (map->getState() == MappingState::OPEN and portCount > 0) {
545 // Close portCount mappings in "OPEN" state.
546 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400547 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400548 portCount--;
549 } else if (map->getState() != MappingState::OPEN) {
550 // If this methods is called, it means there are more open
551 // mappings than required. So, all mappings in a state other
552 // than "OPEN" state (typically in in-progress state) will
553 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400554 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400555 } else {
556 it++;
557 }
558 }
559
560 return true;
561}
562
563void
564UPnPContext::updatePreferredIgd()
565{
Adrien Béraud370257c2023-08-15 20:53:09 -0400566 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400567
568 if (preferredIgd_ and preferredIgd_->isValid())
569 return;
570
571 // Reset and search for the best IGD.
572 preferredIgd_.reset();
573
574 for (auto const& [_, protocol] : protocolList_) {
575 if (protocol->isReady()) {
576 auto igdList = protocol->getIgdList();
577 assert(not igdList.empty());
578 auto const& igd = igdList.front();
579 if (not igd->isValid())
580 continue;
581
582 // Prefer NAT-PMP over PUPNP.
583 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
584 continue;
585
586 // Update.
587 preferredIgd_ = igd;
588 }
589 }
590
591 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400592 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
593 preferredIgd_->getProtocolName(),
594 preferredIgd_->getUID(),
595 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400596 }
597}
598
599std::shared_ptr<IGD>
600UPnPContext::getPreferredIgd() const
601{
Adrien Béraud370257c2023-08-15 20:53:09 -0400602 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400603
604 return preferredIgd_;
605}
606
607void
608UPnPContext::updateMappingList(bool async)
609{
610 // Run async if requested.
611 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400612 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400613 return;
614 }
615
Adrien Béraud370257c2023-08-15 20:53:09 -0400616 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400617
618 // Update the preferred IGD.
619 updatePreferredIgd();
620
Adrien Béraud25c30c42023-07-05 13:46:54 -0400621 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400622
623 // Skip if no controller registered.
624 if (controllerList_.empty())
625 return;
626
627 // Cancel the current timer (if any) and re-schedule.
628 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
629 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400630 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400631 // No valid IGD. Nothing to do.
632 return;
633 }
634
Adrien Berauda8731ac2023-08-17 12:19:39 -0400635 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400636 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
637 if (ec != asio::error::operation_aborted)
638 updateMappingList(false);
639 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400640
641 // Process pending requests if any.
642 processPendingRequests(prefIgd);
643
644 // Make new requests for mappings that failed and have
645 // the auto-update option enabled.
646 processMappingWithAutoUpdate();
647
648 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
649
650 for (auto idx : {0, 1}) {
651 auto type = typeArray[idx];
652
653 MappingStatus status;
654 getMappingStatus(type, status);
655
Adrien Berauda8731ac2023-08-17 12:19:39 -0400656 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
657 "in-progress, {:d} failed",
658 Mapping::getTypeStr(type),
659 status.sum(),
660 status.openCount_,
661 status.readyCount_,
662 status.openCount_ - status.readyCount_,
663 status.pendingCount_,
664 status.inProgressCount_,
665 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400666
667 if (status.failedCount_ > 0) {
668 std::lock_guard<std::mutex> lock(mappingMutex_);
669 auto const& mappingList = getMappingList(type);
670 for (auto const& [_, map] : mappingList) {
671 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400672 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
673 map->toString(true),
674 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400675 }
676 }
677 }
678
679 int toRequestCount = (int) minOpenPortLimit_[idx]
680 - (int) (status.readyCount_ + status.inProgressCount_
681 + status.pendingCount_);
682
683 // Provision/release mappings accordingly.
684 if (toRequestCount > 0) {
685 // Take into account the request in-progress when making
686 // requests for new mappings.
687 provisionNewMappings(type, toRequestCount);
688 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
689 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
690 }
691 }
692
693 // Prune the mapping list if needed
694 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
695#if HAVE_LIBNATPMP
696 // Dont perform if NAT-PMP is valid.
697 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
698#endif
699 {
700 pruneMappingList();
701 }
702 }
703
704#if HAVE_LIBNATPMP
705 // Renew nat-pmp allocations
706 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
707 renewAllocations();
708#endif
709}
710
711void
712UPnPContext::pruneMappingList()
713{
Adrien Béraud370257c2023-08-15 20:53:09 -0400714 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400715
716 MappingStatus status;
717 getMappingStatus(status);
718
719 // Do not prune the list if there are pending/in-progress requests.
720 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
721 return;
722 }
723
724 auto const& igd = getPreferredIgd();
725 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
726 return;
727 }
728 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
729
730 auto remoteMapList = protocol->getMappingsListByDescr(igd,
731 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Berauda0683d12023-08-22 18:09:02 -0400732 /*if (remoteMapList.empty()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400733 std::lock_guard<std::mutex> lock(mappingMutex_);
734 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400735 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400736 }
Adrien Berauda0683d12023-08-22 18:09:02 -0400737 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400738
739 pruneUnMatchedMappings(igd, remoteMapList);
740 pruneUnTrackedMappings(igd, remoteMapList);
741}
742
743void
744UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
745 const std::map<Mapping::key_t, Mapping>& remoteMapList)
746{
747 // Check/synchronize local mapping list with the list
748 // returned by the IGD.
749
Adrien Berauda0683d12023-08-22 18:09:02 -0400750 for (auto type: {PortType::TCP, PortType::UDP}) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400751 // Use a temporary list to avoid processing mappings while holding the lock.
752 std::list<Mapping::sharedPtr_t> toRemoveList;
753 {
754 std::lock_guard<std::mutex> lock(mappingMutex_);
Adrien Berauda0683d12023-08-22 18:09:02 -0400755 for (auto const& [_, map] : getMappingList(type)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400756 // Only check mappings allocated by UPNP protocol.
757 if (map->getProtocol() != NatProtocolType::PUPNP) {
758 continue;
759 }
760 // Set mapping as failed if not found in the list
761 // returned by the IGD.
762 if (map->getState() == MappingState::OPEN
763 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
764 toRemoveList.emplace_back(map);
765
Adrien Berauda8731ac2023-08-17 12:19:39 -0400766 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
767 "remote list. Mark as failed!",
768 map->toString(),
769 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400770 }
771 }
772 }
773
774 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400775 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400776 unregisterMapping(map);
777 }
778 }
779}
780
781void
782UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
783 const std::map<Mapping::key_t, Mapping>& remoteMapList)
784{
785 // Use a temporary list to avoid processing mappings while holding the lock.
786 std::list<Mapping> toRemoveList;
787 {
788 std::lock_guard<std::mutex> lock(mappingMutex_);
789
790 for (auto const& [_, map] : remoteMapList) {
791 // Must has valid IGD pointer and use UPNP protocol.
792 assert(map.getIgd());
793 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
794 auto& mappingList = getMappingList(map.getType());
795 auto it = mappingList.find(map.getMapKey());
796 if (it == mappingList.end()) {
797 // Not present, request mapping remove.
798 toRemoveList.emplace_back(std::move(map));
799 // Make only few remove requests at once.
800 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
801 break;
802 }
803 }
804 }
805
806 // Remove un-tracked mappings.
807 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
808 for (auto const& map : toRemoveList) {
809 protocol->requestMappingRemove(map);
810 }
811}
812
813void
814UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
815{
Adrien Béraud370257c2023-08-15 20:53:09 -0400816 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400817
818 // Use temporary list to avoid holding the lock while
819 // processing the mapping list.
820 std::list<Mapping::sharedPtr_t> toRemoveList;
821 {
822 std::lock_guard<std::mutex> lock(mappingMutex_);
823
824 PortType types[2] {PortType::TCP, PortType::UDP};
825 for (auto& type : types) {
826 auto& mappingList = getMappingList(type);
827 for (auto const& [_, map] : mappingList) {
828 if (map->getIgd() == igd)
829 toRemoveList.emplace_back(map);
830 }
831 }
832 }
833
834 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400835 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
836 map->toString(),
837 igd->toString(),
838 igd->getProtocolName());
Adrien Béraud56740312023-08-23 08:38:28 -0400839 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400840 unregisterMapping(map);
841 }
842}
843
844void
845UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
846{
847 // This list holds the mappings to be requested. This is
848 // needed to avoid performing the requests while holding
849 // the lock.
850 std::list<Mapping::sharedPtr_t> requestsList;
851
852 // Populate the list of requests to perform.
853 {
854 std::lock_guard<std::mutex> lock(mappingMutex_);
855 PortType typeArray[2] {PortType::TCP, PortType::UDP};
856
857 for (auto type : typeArray) {
858 auto& mappingList = getMappingList(type);
859 for (auto& [_, map] : mappingList) {
860 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400861 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
862 map->toString(),
863 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400864 requestsList.emplace_back(map);
865 }
866 }
867 }
868 }
869
870 // Process the pending requests.
871 for (auto const& map : requestsList) {
872 requestMapping(map);
873 }
874}
875
876void
877UPnPContext::processMappingWithAutoUpdate()
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 for mappings with auto-update enabled.
885 {
886 std::lock_guard<std::mutex> lock(mappingMutex_);
887 PortType typeArray[2] {PortType::TCP, PortType::UDP};
888
889 for (auto type : typeArray) {
890 auto& mappingList = getMappingList(type);
891 for (auto const& [_, map] : mappingList) {
892 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
893 requestsList.emplace_back(map);
894 }
895 }
896 }
897 }
898
899 for (auto const& oldMap : requestsList) {
900 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400901 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
902 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400903
904 // Reserve a new mapping.
905 Mapping newMapping(oldMap->getType());
906 newMapping.enableAutoUpdate(true);
907 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
908
909 auto const& mapPtr = reserveMapping(newMapping);
910 assert(mapPtr);
911
912 // Release the old one.
913 oldMap->setAvailable(true);
914 oldMap->enableAutoUpdate(false);
915 oldMap->setNotifyCallback(nullptr);
916 unregisterMapping(oldMap);
917 }
918}
919
920void
921UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
922{
923 assert(igd);
924
Adrien Béraud612b55b2023-05-29 10:42:04 -0400925 // Reset to start search for a new best IGD.
926 preferredIgd_.reset();
927
928 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
929 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
930 : "INVALID";
931
932 auto const& igdLocalAddr = igd->getLocalIp();
933 auto protocolName = igd->getProtocolName();
934
Adrien Berauda8731ac2023-08-17 12:19:39 -0400935 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
936 igd->getUID(),
937 igd->toString(),
938 protocolName,
939 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400940
941 // Check if the IGD has valid addresses.
942 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400943 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400944 return;
945 }
946
947 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400948 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400949 return;
950 }
951
952 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400953 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
954 " The mapped addresses might not be reachable",
955 protocolName,
956 igd->getPublicIp().toString(),
957 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400958 }
959
960 // The IGD was removed or is invalid.
961 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400962 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
963 igd->getUID(),
964 igd->toString(),
965 protocolName,
966 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400967
968 pruneMappingsWithInvalidIgds(igd);
969
970 std::lock_guard<std::mutex> lock(mappingMutex_);
971 validIgdList_.erase(igd);
972 return;
973 }
974
975 // Update the IGD list.
976 {
977 std::lock_guard<std::mutex> lock(mappingMutex_);
978 auto ret = validIgdList_.emplace(igd);
979 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400980 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
981 protocolName,
982 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400983 } else {
984 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400985 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
986 protocolName,
987 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400988 return;
989 }
990 }
991
992 // Update the provisionned mappings.
993 updateMappingList(false);
994}
995
996void
997UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
998{
Adrien Béraud370257c2023-08-15 20:53:09 -0400999 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001000
1001 // Check if we have a pending request for this response.
1002 auto map = getMappingWithKey(mapRes.getMapKey());
1003 if (not map) {
1004 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001005 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1006 mapRes.toString(),
1007 igd->toString(),
1008 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001009 return;
1010 }
1011
1012 // The mapping request is new and successful. Update.
1013 map->setIgd(igd);
1014 map->setInternalAddress(mapRes.getInternalAddress());
1015 map->setExternalPort(mapRes.getExternalPort());
1016
1017 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001018 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001019
Adrien Berauda8731ac2023-08-17 12:19:39 -04001020 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1021 map->toString(),
1022 igd->toString(),
1023 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001024
1025 // Call setValid() to reset the errors counter. We need
1026 // to reset the counter on each successful response.
1027 igd->setValid(true);
1028}
1029
1030#if HAVE_LIBNATPMP
1031void
1032UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1033{
1034 auto mapPtr = getMappingWithKey(map.getMapKey());
1035
1036 if (not mapPtr) {
1037 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001038 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1039 map.toString(),
1040 igd->toString(),
1041 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001042 return;
1043 }
1044 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1045 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001046 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1047 mapPtr->toString(),
1048 igd->toString(),
1049 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001050 return;
1051 }
1052
1053 mapPtr->setRenewalTime(map.getRenewalTime());
1054}
1055#endif
1056
1057void
1058UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1059{
Adrien Béraud370257c2023-08-15 20:53:09 -04001060 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001061 // Silently ignore if the mapping is invalid
1062 return;
1063 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001064 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1065 protocol->requestMappingRemove(*map);
1066}
1067
1068void
1069UPnPContext::deleteAllMappings(PortType type)
1070{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001071 std::lock_guard<std::mutex> lock(mappingMutex_);
1072 auto& mappingList = getMappingList(type);
1073
1074 for (auto const& [_, map] : mappingList) {
1075 requestRemoveMapping(map);
1076 }
1077}
1078
1079void
1080UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1081{
1082 if (not mapRes.isValid())
1083 return;
1084
Adrien Béraud612b55b2023-05-29 10:42:04 -04001085 auto map = getMappingWithKey(mapRes.getMapKey());
1086 // Notify the listener.
1087 if (map and map->getNotifyCallback())
1088 map->getNotifyCallback()(map);
1089}
1090
1091Mapping::sharedPtr_t
1092UPnPContext::registerMapping(Mapping& map)
1093{
1094 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001095 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001096 auto port = getAvailablePortNumber(map.getType());
1097 map.setExternalPort(port);
1098 map.setInternalPort(port);
1099 }
1100
1101 // Newly added mapping must be in pending state by default.
1102 map.setState(MappingState::PENDING);
1103
1104 Mapping::sharedPtr_t mapPtr;
1105
1106 {
1107 std::lock_guard<std::mutex> lock(mappingMutex_);
1108 auto& mappingList = getMappingList(map.getType());
1109
1110 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1111 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001112 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001113 return {};
1114 }
1115 mapPtr = ret.first->second;
1116 assert(mapPtr);
1117 }
1118
1119 // No available IGD. The pending mapping requests will be processed
1120 // when a IGD becomes available (in onIgdAdded() method).
1121 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001122 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001123 } else {
1124 requestMapping(mapPtr);
1125 }
1126
1127 return mapPtr;
1128}
1129
Adrien Béraud612b55b2023-05-29 10:42:04 -04001130void
1131UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1132{
Adrien Béraud370257c2023-08-15 20:53:09 -04001133 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001134
1135 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001136 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001137 return;
1138 }
1139
1140 if (map->getAutoUpdate()) {
1141 // Dont unregister mappings with auto-update enabled.
1142 return;
1143 }
1144 auto& mappingList = getMappingList(map->getType());
1145
1146 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001147 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001148 } else {
1149 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001150 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1151 map->toString(),
1152 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001153 }
1154}
1155
1156std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1157UPnPContext::getMappingList(PortType type)
1158{
1159 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1160 return mappingList_[typeIdx];
1161}
1162
1163Mapping::sharedPtr_t
1164UPnPContext::getMappingWithKey(Mapping::key_t key)
1165{
1166 std::lock_guard<std::mutex> lock(mappingMutex_);
1167 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1168 auto it = mappingList.find(key);
1169 if (it == mappingList.end())
1170 return nullptr;
1171 return it->second;
1172}
1173
1174void
1175UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1176{
1177 std::lock_guard<std::mutex> lock(mappingMutex_);
1178 auto& mappingList = getMappingList(type);
1179
1180 for (auto const& [_, map] : mappingList) {
1181 switch (map->getState()) {
1182 case MappingState::PENDING: {
1183 status.pendingCount_++;
1184 break;
1185 }
1186 case MappingState::IN_PROGRESS: {
1187 status.inProgressCount_++;
1188 break;
1189 }
1190 case MappingState::FAILED: {
1191 status.failedCount_++;
1192 break;
1193 }
1194 case MappingState::OPEN: {
1195 status.openCount_++;
1196 if (map->isAvailable())
1197 status.readyCount_++;
1198 break;
1199 }
1200
1201 default:
1202 // Must not get here.
1203 assert(false);
1204 break;
1205 }
1206 }
1207}
1208
1209void
1210UPnPContext::getMappingStatus(MappingStatus& status)
1211{
1212 getMappingStatus(PortType::TCP, status);
1213 getMappingStatus(PortType::UDP, status);
1214}
1215
1216void
1217UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1218{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001219 auto const& map = getMappingWithKey(mapRes.getMapKey());
1220 if (not map) {
1221 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001222 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1223 mapRes.toString(),
1224 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001225 return;
1226 }
1227
1228 auto igd = map->getIgd();
1229 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001230 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001231 return;
1232 }
1233
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001234 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001235 unregisterMapping(map);
1236
Adrien Berauda8731ac2023-08-17 12:19:39 -04001237 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1238 map->toString(),
1239 igd->toString(),
1240 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001241}
1242
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001243void
1244UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1245{
1246 // CHECK_VALID_THREAD();
1247
1248 assert(map);
1249
1250 // Ignore if the state did not change.
1251 if (newState == map->getState()) {
1252 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1253 return;
1254 }
1255
1256 // Update the state.
1257 map->setState(newState);
1258
1259 // Notify the listener if set.
1260 if (notify and map->getNotifyCallback())
1261 map->getNotifyCallback()(map);
1262}
1263
Adrien Béraud612b55b2023-05-29 10:42:04 -04001264#if HAVE_LIBNATPMP
1265void
1266UPnPContext::renewAllocations()
1267{
Adrien Béraud370257c2023-08-15 20:53:09 -04001268 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001269
1270 // Check if the we have valid PMP IGD.
1271 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1272
1273 auto now = sys_clock::now();
1274 std::vector<Mapping::sharedPtr_t> toRenew;
1275
1276 for (auto type : {PortType::TCP, PortType::UDP}) {
1277 std::lock_guard<std::mutex> lock(mappingMutex_);
1278 auto mappingList = getMappingList(type);
1279 for (auto const& [_, map] : mappingList) {
1280 if (not map->isValid())
1281 continue;
1282 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1283 continue;
1284 if (map->getState() != MappingState::OPEN)
1285 continue;
1286 if (now < map->getRenewalTime())
1287 continue;
1288
1289 toRenew.emplace_back(map);
1290 }
1291 }
1292
1293 // Quit if there are no mapping to renew
1294 if (toRenew.empty())
1295 return;
1296
1297 for (auto const& map : toRenew) {
1298 pmpProto->requestMappingRenew(*map);
1299 }
1300}
1301#endif
1302
1303} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001304} // namespace dhtnet