blob: a69528b19792532ebdcd6a1a978d07c51c247e7a [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); });
107
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_) {
117 if (logger_) logger_->debug("UPnPContext: stopping io_context thread");
118 ctx->stop();
119 ioContextRunner_->join();
120 ioContextRunner_.reset();
121 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400122}
123
124UPnPContext::~UPnPContext()
125{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400126 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400127}
128
129void
130UPnPContext::init()
131{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400132#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400133 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134 natPmp->setObserver(this);
135 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
136#endif
137
138#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400139 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400140 pupnp->setObserver(this);
141 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
142#endif
143}
144
145void
146UPnPContext::startUpnp()
147{
148 assert(not controllerList_.empty());
149
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400150 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400151
152 // Request a new IGD search.
153 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400154 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400155 }
156
157 started_ = true;
158}
159
160void
161UPnPContext::stopUpnp(bool forceRelease)
162{
Adrien Béraud370257c2023-08-15 20:53:09 -0400163 /*if (not isValidThread()) {
164 ctx->post([this, forceRelease] { stopUpnp(forceRelease); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400165 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400166 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400167
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] {
395 auto mapPtr = getMappingWithKey(map.getMapKey());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400396
Adrien Béraudc36965c2023-08-17 21:50:27 -0400397 if (not mapPtr) {
398 // Might happen if the mapping failed or was never granted.
399 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
400 return;
401 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400402
Adrien Béraudc36965c2023-08-17 21:50:27 -0400403 if (mapPtr->isAvailable()) {
404 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
405 return;
406 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400407
Adrien Béraudc36965c2023-08-17 21:50:27 -0400408 // Remove it.
409 requestRemoveMapping(mapPtr);
410 unregisterMapping(mapPtr);
411 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400412}
413
414void
415UPnPContext::registerController(void* controller)
416{
417 {
418 std::lock_guard<std::mutex> lock(mappingMutex_);
419 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400420 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400421 return;
422 }
Adrien Béraudc36965c2023-08-17 21:50:27 -0400423 auto ret = controllerList_.emplace(controller);
424 if (not ret.second) {
425 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
426 return;
427 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400428 }
429
Adrien Berauda8731ac2023-08-17 12:19:39 -0400430 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400431 if (not started_)
432 startUpnp();
433}
434
435void
436UPnPContext::unregisterController(void* controller)
437{
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);
476
Adrien Béraud370257c2023-08-15 20:53:09 -0400477 /*if (not isValidThread()) {
478 ctx->post([this, map] { requestMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400479 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400480 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400481
482 auto const& igd = getPreferredIgd();
483 // We must have at least a valid IGD pointer if we get here.
484 // Not this method is called only if there were a valid IGD, however,
485 // because the processing is asynchronous, it's possible that the IGD
486 // was invalidated when the this code executed.
487 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400488 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400489 return;
490 }
491
492 map->setIgd(igd);
493
Adrien Berauda8731ac2023-08-17 12:19:39 -0400494 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
495 map->toString(),
496 igd->getProtocolName(),
497 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400498
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400499 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400500
501 auto const& protocol = protocolList_.at(igd->getProtocol());
502 protocol->requestMappingAdd(*map);
503}
504
505bool
506UPnPContext::provisionNewMappings(PortType type, int portCount)
507{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400508 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400509
510 assert(portCount > 0);
511
512 while (portCount > 0) {
513 auto port = getAvailablePortNumber(type);
514 if (port > 0) {
515 // Found an available port number
516 portCount--;
517 Mapping map(type, port, port, true);
518 registerMapping(map);
519 } else {
520 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400521 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400522 return false;
523 }
524 }
525
526 return true;
527}
528
529bool
530UPnPContext::deleteUnneededMappings(PortType type, int portCount)
531{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400532 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400533
534 assert(portCount > 0);
535
Adrien Béraud370257c2023-08-15 20:53:09 -0400536 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400537
538 std::lock_guard<std::mutex> lock(mappingMutex_);
539 auto& mappingList = getMappingList(type);
540
541 for (auto it = mappingList.begin(); it != mappingList.end();) {
542 auto map = it->second;
543 assert(map);
544
545 if (not map->isAvailable()) {
546 it++;
547 continue;
548 }
549
550 if (map->getState() == MappingState::OPEN and portCount > 0) {
551 // Close portCount mappings in "OPEN" state.
552 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400553 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400554 portCount--;
555 } else if (map->getState() != MappingState::OPEN) {
556 // If this methods is called, it means there are more open
557 // mappings than required. So, all mappings in a state other
558 // than "OPEN" state (typically in in-progress state) will
559 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400560 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400561 } else {
562 it++;
563 }
564 }
565
566 return true;
567}
568
569void
570UPnPContext::updatePreferredIgd()
571{
Adrien Béraud370257c2023-08-15 20:53:09 -0400572 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400573
574 if (preferredIgd_ and preferredIgd_->isValid())
575 return;
576
577 // Reset and search for the best IGD.
578 preferredIgd_.reset();
579
580 for (auto const& [_, protocol] : protocolList_) {
581 if (protocol->isReady()) {
582 auto igdList = protocol->getIgdList();
583 assert(not igdList.empty());
584 auto const& igd = igdList.front();
585 if (not igd->isValid())
586 continue;
587
588 // Prefer NAT-PMP over PUPNP.
589 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
590 continue;
591
592 // Update.
593 preferredIgd_ = igd;
594 }
595 }
596
597 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400598 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
599 preferredIgd_->getProtocolName(),
600 preferredIgd_->getUID(),
601 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400602 }
603}
604
605std::shared_ptr<IGD>
606UPnPContext::getPreferredIgd() const
607{
Adrien Béraud370257c2023-08-15 20:53:09 -0400608 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400609
610 return preferredIgd_;
611}
612
613void
614UPnPContext::updateMappingList(bool async)
615{
616 // Run async if requested.
617 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400618 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400619 return;
620 }
621
Adrien Béraud370257c2023-08-15 20:53:09 -0400622 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400623
624 // Update the preferred IGD.
625 updatePreferredIgd();
626
Adrien Béraud25c30c42023-07-05 13:46:54 -0400627 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400628
629 // Skip if no controller registered.
630 if (controllerList_.empty())
631 return;
632
633 // Cancel the current timer (if any) and re-schedule.
634 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
635 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400636 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400637 // No valid IGD. Nothing to do.
638 return;
639 }
640
Adrien Berauda8731ac2023-08-17 12:19:39 -0400641 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400642 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
643 if (ec != asio::error::operation_aborted)
644 updateMappingList(false);
645 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400646
647 // Process pending requests if any.
648 processPendingRequests(prefIgd);
649
650 // Make new requests for mappings that failed and have
651 // the auto-update option enabled.
652 processMappingWithAutoUpdate();
653
654 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
655
656 for (auto idx : {0, 1}) {
657 auto type = typeArray[idx];
658
659 MappingStatus status;
660 getMappingStatus(type, status);
661
Adrien Berauda8731ac2023-08-17 12:19:39 -0400662 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
663 "in-progress, {:d} failed",
664 Mapping::getTypeStr(type),
665 status.sum(),
666 status.openCount_,
667 status.readyCount_,
668 status.openCount_ - status.readyCount_,
669 status.pendingCount_,
670 status.inProgressCount_,
671 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400672
673 if (status.failedCount_ > 0) {
674 std::lock_guard<std::mutex> lock(mappingMutex_);
675 auto const& mappingList = getMappingList(type);
676 for (auto const& [_, map] : mappingList) {
677 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400678 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
679 map->toString(true),
680 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400681 }
682 }
683 }
684
685 int toRequestCount = (int) minOpenPortLimit_[idx]
686 - (int) (status.readyCount_ + status.inProgressCount_
687 + status.pendingCount_);
688
689 // Provision/release mappings accordingly.
690 if (toRequestCount > 0) {
691 // Take into account the request in-progress when making
692 // requests for new mappings.
693 provisionNewMappings(type, toRequestCount);
694 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
695 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
696 }
697 }
698
699 // Prune the mapping list if needed
700 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
701#if HAVE_LIBNATPMP
702 // Dont perform if NAT-PMP is valid.
703 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
704#endif
705 {
706 pruneMappingList();
707 }
708 }
709
710#if HAVE_LIBNATPMP
711 // Renew nat-pmp allocations
712 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
713 renewAllocations();
714#endif
715}
716
717void
718UPnPContext::pruneMappingList()
719{
Adrien Béraud370257c2023-08-15 20:53:09 -0400720 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400721
722 MappingStatus status;
723 getMappingStatus(status);
724
725 // Do not prune the list if there are pending/in-progress requests.
726 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
727 return;
728 }
729
730 auto const& igd = getPreferredIgd();
731 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
732 return;
733 }
734 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
735
736 auto remoteMapList = protocol->getMappingsListByDescr(igd,
737 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Berauda0683d12023-08-22 18:09:02 -0400738 /*if (remoteMapList.empty()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400739 std::lock_guard<std::mutex> lock(mappingMutex_);
740 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400741 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400742 }
Adrien Berauda0683d12023-08-22 18:09:02 -0400743 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400744
745 pruneUnMatchedMappings(igd, remoteMapList);
746 pruneUnTrackedMappings(igd, remoteMapList);
747}
748
749void
750UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
751 const std::map<Mapping::key_t, Mapping>& remoteMapList)
752{
753 // Check/synchronize local mapping list with the list
754 // returned by the IGD.
755
Adrien Berauda0683d12023-08-22 18:09:02 -0400756 for (auto type: {PortType::TCP, PortType::UDP}) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400757 // Use a temporary list to avoid processing mappings while holding the lock.
758 std::list<Mapping::sharedPtr_t> toRemoveList;
759 {
760 std::lock_guard<std::mutex> lock(mappingMutex_);
Adrien Berauda0683d12023-08-22 18:09:02 -0400761 for (auto const& [_, map] : getMappingList(type)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400762 // Only check mappings allocated by UPNP protocol.
763 if (map->getProtocol() != NatProtocolType::PUPNP) {
764 continue;
765 }
766 // Set mapping as failed if not found in the list
767 // returned by the IGD.
768 if (map->getState() == MappingState::OPEN
769 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
770 toRemoveList.emplace_back(map);
771
Adrien Berauda8731ac2023-08-17 12:19:39 -0400772 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
773 "remote list. Mark as failed!",
774 map->toString(),
775 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400776 }
777 }
778 }
779
780 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400781 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400782 unregisterMapping(map);
783 }
784 }
785}
786
787void
788UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
789 const std::map<Mapping::key_t, Mapping>& remoteMapList)
790{
791 // Use a temporary list to avoid processing mappings while holding the lock.
792 std::list<Mapping> toRemoveList;
793 {
794 std::lock_guard<std::mutex> lock(mappingMutex_);
795
796 for (auto const& [_, map] : remoteMapList) {
797 // Must has valid IGD pointer and use UPNP protocol.
798 assert(map.getIgd());
799 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
800 auto& mappingList = getMappingList(map.getType());
801 auto it = mappingList.find(map.getMapKey());
802 if (it == mappingList.end()) {
803 // Not present, request mapping remove.
804 toRemoveList.emplace_back(std::move(map));
805 // Make only few remove requests at once.
806 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
807 break;
808 }
809 }
810 }
811
812 // Remove un-tracked mappings.
813 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
814 for (auto const& map : toRemoveList) {
815 protocol->requestMappingRemove(map);
816 }
817}
818
819void
820UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
821{
Adrien Béraud370257c2023-08-15 20:53:09 -0400822 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400823
824 // Use temporary list to avoid holding the lock while
825 // processing the mapping list.
826 std::list<Mapping::sharedPtr_t> toRemoveList;
827 {
828 std::lock_guard<std::mutex> lock(mappingMutex_);
829
830 PortType types[2] {PortType::TCP, PortType::UDP};
831 for (auto& type : types) {
832 auto& mappingList = getMappingList(type);
833 for (auto const& [_, map] : mappingList) {
834 if (map->getIgd() == igd)
835 toRemoveList.emplace_back(map);
836 }
837 }
838 }
839
840 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400841 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
842 map->toString(),
843 igd->toString(),
844 igd->getProtocolName());
Adrien Béraud56740312023-08-23 08:38:28 -0400845 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400846 unregisterMapping(map);
847 }
848}
849
850void
851UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
852{
853 // This list holds the mappings to be requested. This is
854 // needed to avoid performing the requests while holding
855 // the lock.
856 std::list<Mapping::sharedPtr_t> requestsList;
857
858 // Populate the list of requests to perform.
859 {
860 std::lock_guard<std::mutex> lock(mappingMutex_);
861 PortType typeArray[2] {PortType::TCP, PortType::UDP};
862
863 for (auto type : typeArray) {
864 auto& mappingList = getMappingList(type);
865 for (auto& [_, map] : mappingList) {
866 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400867 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
868 map->toString(),
869 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400870 requestsList.emplace_back(map);
871 }
872 }
873 }
874 }
875
876 // Process the pending requests.
877 for (auto const& map : requestsList) {
878 requestMapping(map);
879 }
880}
881
882void
883UPnPContext::processMappingWithAutoUpdate()
884{
885 // This list holds the mappings to be requested. This is
886 // needed to avoid performing the requests while holding
887 // the lock.
888 std::list<Mapping::sharedPtr_t> requestsList;
889
890 // Populate the list of requests for mappings with auto-update enabled.
891 {
892 std::lock_guard<std::mutex> lock(mappingMutex_);
893 PortType typeArray[2] {PortType::TCP, PortType::UDP};
894
895 for (auto type : typeArray) {
896 auto& mappingList = getMappingList(type);
897 for (auto const& [_, map] : mappingList) {
898 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
899 requestsList.emplace_back(map);
900 }
901 }
902 }
903 }
904
905 for (auto const& oldMap : requestsList) {
906 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400907 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
908 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400909
910 // Reserve a new mapping.
911 Mapping newMapping(oldMap->getType());
912 newMapping.enableAutoUpdate(true);
913 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
914
915 auto const& mapPtr = reserveMapping(newMapping);
916 assert(mapPtr);
917
918 // Release the old one.
919 oldMap->setAvailable(true);
920 oldMap->enableAutoUpdate(false);
921 oldMap->setNotifyCallback(nullptr);
922 unregisterMapping(oldMap);
923 }
924}
925
926void
927UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
928{
929 assert(igd);
930
Adrien Béraud370257c2023-08-15 20:53:09 -0400931 /*if (not isValidThread()) {
932 ctx->post([this, igd, event] { onIgdUpdated(igd, event); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400933 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400934 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400935
936 // Reset to start search for a new best IGD.
937 preferredIgd_.reset();
938
939 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
940 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
941 : "INVALID";
942
943 auto const& igdLocalAddr = igd->getLocalIp();
944 auto protocolName = igd->getProtocolName();
945
Adrien Berauda8731ac2023-08-17 12:19:39 -0400946 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
947 igd->getUID(),
948 igd->toString(),
949 protocolName,
950 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400951
952 // Check if the IGD has valid addresses.
953 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400954 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400955 return;
956 }
957
958 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400959 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400960 return;
961 }
962
963 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400964 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
965 " The mapped addresses might not be reachable",
966 protocolName,
967 igd->getPublicIp().toString(),
968 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400969 }
970
971 // The IGD was removed or is invalid.
972 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400973 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
974 igd->getUID(),
975 igd->toString(),
976 protocolName,
977 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400978
979 pruneMappingsWithInvalidIgds(igd);
980
981 std::lock_guard<std::mutex> lock(mappingMutex_);
982 validIgdList_.erase(igd);
983 return;
984 }
985
986 // Update the IGD list.
987 {
988 std::lock_guard<std::mutex> lock(mappingMutex_);
989 auto ret = validIgdList_.emplace(igd);
990 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400991 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
992 protocolName,
993 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400994 } else {
995 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400996 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
997 protocolName,
998 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400999 return;
1000 }
1001 }
1002
1003 // Update the provisionned mappings.
1004 updateMappingList(false);
1005}
1006
1007void
1008UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1009{
Adrien Béraud370257c2023-08-15 20:53:09 -04001010 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001011
1012 // Check if we have a pending request for this response.
1013 auto map = getMappingWithKey(mapRes.getMapKey());
1014 if (not map) {
1015 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001016 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1017 mapRes.toString(),
1018 igd->toString(),
1019 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001020 return;
1021 }
1022
1023 // The mapping request is new and successful. Update.
1024 map->setIgd(igd);
1025 map->setInternalAddress(mapRes.getInternalAddress());
1026 map->setExternalPort(mapRes.getExternalPort());
1027
1028 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001029 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001030
Adrien Berauda8731ac2023-08-17 12:19:39 -04001031 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1032 map->toString(),
1033 igd->toString(),
1034 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001035
1036 // Call setValid() to reset the errors counter. We need
1037 // to reset the counter on each successful response.
1038 igd->setValid(true);
1039}
1040
1041#if HAVE_LIBNATPMP
1042void
1043UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1044{
1045 auto mapPtr = getMappingWithKey(map.getMapKey());
1046
1047 if (not mapPtr) {
1048 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001049 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1050 map.toString(),
1051 igd->toString(),
1052 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001053 return;
1054 }
1055 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1056 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001057 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1058 mapPtr->toString(),
1059 igd->toString(),
1060 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001061 return;
1062 }
1063
1064 mapPtr->setRenewalTime(map.getRenewalTime());
1065}
1066#endif
1067
1068void
1069UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1070{
Adrien Béraud370257c2023-08-15 20:53:09 -04001071 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001072 // Silently ignore if the mapping is invalid
1073 return;
1074 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001075 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1076 protocol->requestMappingRemove(*map);
1077}
1078
1079void
1080UPnPContext::deleteAllMappings(PortType type)
1081{
Adrien Béraud370257c2023-08-15 20:53:09 -04001082 /*if (not isValidThread()) {
1083 ctx->post([this, type] { deleteAllMappings(type); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001084 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001085 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001086
1087 std::lock_guard<std::mutex> lock(mappingMutex_);
1088 auto& mappingList = getMappingList(type);
1089
1090 for (auto const& [_, map] : mappingList) {
1091 requestRemoveMapping(map);
1092 }
1093}
1094
1095void
1096UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1097{
1098 if (not mapRes.isValid())
1099 return;
1100
Adrien Béraud370257c2023-08-15 20:53:09 -04001101 /*if (not isValidThread()) {
1102 ctx->post([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001103 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001104 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001105
1106 auto map = getMappingWithKey(mapRes.getMapKey());
1107 // Notify the listener.
1108 if (map and map->getNotifyCallback())
1109 map->getNotifyCallback()(map);
1110}
1111
1112Mapping::sharedPtr_t
1113UPnPContext::registerMapping(Mapping& map)
1114{
1115 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001116 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001117 auto port = getAvailablePortNumber(map.getType());
1118 map.setExternalPort(port);
1119 map.setInternalPort(port);
1120 }
1121
1122 // Newly added mapping must be in pending state by default.
1123 map.setState(MappingState::PENDING);
1124
1125 Mapping::sharedPtr_t mapPtr;
1126
1127 {
1128 std::lock_guard<std::mutex> lock(mappingMutex_);
1129 auto& mappingList = getMappingList(map.getType());
1130
1131 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1132 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001133 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001134 return {};
1135 }
1136 mapPtr = ret.first->second;
1137 assert(mapPtr);
1138 }
1139
1140 // No available IGD. The pending mapping requests will be processed
1141 // when a IGD becomes available (in onIgdAdded() method).
1142 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001143 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001144 } else {
1145 requestMapping(mapPtr);
1146 }
1147
1148 return mapPtr;
1149}
1150
Adrien Béraud612b55b2023-05-29 10:42:04 -04001151void
1152UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1153{
Adrien Béraud370257c2023-08-15 20:53:09 -04001154 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001155
1156 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001157 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001158 return;
1159 }
1160
1161 if (map->getAutoUpdate()) {
1162 // Dont unregister mappings with auto-update enabled.
1163 return;
1164 }
1165 auto& mappingList = getMappingList(map->getType());
1166
1167 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001168 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001169 } else {
1170 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001171 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1172 map->toString(),
1173 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001174 }
1175}
1176
1177std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1178UPnPContext::getMappingList(PortType type)
1179{
1180 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1181 return mappingList_[typeIdx];
1182}
1183
1184Mapping::sharedPtr_t
1185UPnPContext::getMappingWithKey(Mapping::key_t key)
1186{
1187 std::lock_guard<std::mutex> lock(mappingMutex_);
1188 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1189 auto it = mappingList.find(key);
1190 if (it == mappingList.end())
1191 return nullptr;
1192 return it->second;
1193}
1194
1195void
1196UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1197{
1198 std::lock_guard<std::mutex> lock(mappingMutex_);
1199 auto& mappingList = getMappingList(type);
1200
1201 for (auto const& [_, map] : mappingList) {
1202 switch (map->getState()) {
1203 case MappingState::PENDING: {
1204 status.pendingCount_++;
1205 break;
1206 }
1207 case MappingState::IN_PROGRESS: {
1208 status.inProgressCount_++;
1209 break;
1210 }
1211 case MappingState::FAILED: {
1212 status.failedCount_++;
1213 break;
1214 }
1215 case MappingState::OPEN: {
1216 status.openCount_++;
1217 if (map->isAvailable())
1218 status.readyCount_++;
1219 break;
1220 }
1221
1222 default:
1223 // Must not get here.
1224 assert(false);
1225 break;
1226 }
1227 }
1228}
1229
1230void
1231UPnPContext::getMappingStatus(MappingStatus& status)
1232{
1233 getMappingStatus(PortType::TCP, status);
1234 getMappingStatus(PortType::UDP, status);
1235}
1236
1237void
1238UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1239{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001240 auto const& map = getMappingWithKey(mapRes.getMapKey());
1241 if (not map) {
1242 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001243 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1244 mapRes.toString(),
1245 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001246 return;
1247 }
1248
1249 auto igd = map->getIgd();
1250 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001251 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001252 return;
1253 }
1254
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001255 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001256 unregisterMapping(map);
1257
Adrien Berauda8731ac2023-08-17 12:19:39 -04001258 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1259 map->toString(),
1260 igd->toString(),
1261 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001262}
1263
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001264void
1265UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1266{
1267 // CHECK_VALID_THREAD();
1268
1269 assert(map);
1270
1271 // Ignore if the state did not change.
1272 if (newState == map->getState()) {
1273 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1274 return;
1275 }
1276
1277 // Update the state.
1278 map->setState(newState);
1279
1280 // Notify the listener if set.
1281 if (notify and map->getNotifyCallback())
1282 map->getNotifyCallback()(map);
1283}
1284
Adrien Béraud612b55b2023-05-29 10:42:04 -04001285#if HAVE_LIBNATPMP
1286void
1287UPnPContext::renewAllocations()
1288{
Adrien Béraud370257c2023-08-15 20:53:09 -04001289 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001290
1291 // Check if the we have valid PMP IGD.
1292 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1293
1294 auto now = sys_clock::now();
1295 std::vector<Mapping::sharedPtr_t> toRenew;
1296
1297 for (auto type : {PortType::TCP, PortType::UDP}) {
1298 std::lock_guard<std::mutex> lock(mappingMutex_);
1299 auto mappingList = getMappingList(type);
1300 for (auto const& [_, map] : mappingList) {
1301 if (not map->isValid())
1302 continue;
1303 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1304 continue;
1305 if (map->getState() != MappingState::OPEN)
1306 continue;
1307 if (now < map->getRenewalTime())
1308 continue;
1309
1310 toRenew.emplace_back(map);
1311 }
1312 }
1313
1314 // Quit if there are no mapping to renew
1315 if (toRenew.empty())
1316 return;
1317
1318 for (auto const& map : toRenew) {
1319 pmpProto->requestMappingRenew(*map);
1320 }
1321}
1322#endif
1323
1324} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001325} // namespace dhtnet