blob: 9d79f49520dd782f375895a87cc00a5eb6664b65 [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
26
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éraudb04fbd72023-08-17 19:56:11 -040047 : ctx(createIoContext(ioContext, logger)), mappingListUpdateTimer_(*ioContext), logger_(logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -040048{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040049 if (logger_) logger_->debug("Creating UPnPContext instance [{}]", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -040050
51 // Set port ranges
52 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
53 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
54
Adrien Béraud370257c2023-08-15 20:53:09 -040055 ctx->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040056}
57
Adrien Béraudb04fbd72023-08-17 19:56:11 -040058std::shared_ptr<asio::io_context>
59UPnPContext::createIoContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger) {
60 if (ctx) {
61 return ctx;
62 } else {
63 if (logger) logger->debug("UPnPContext: starting dedicated io_context thread");
64 auto ioCtx = std::make_shared<asio::io_context>();
65 ioContextRunner_ = std::make_unique<std::thread>([ioCtx, l=logger]() {
66 try {
67 auto work = asio::make_work_guard(*ioCtx);
68 ioCtx->run();
69 } catch (const std::exception& ex) {
70 if (l) l->error("Unexpected io_context thread exception: {}", ex.what());
71 }
72 });
73 return ioCtx;
74 }
75}
76
Adrien Béraud612b55b2023-05-29 10:42:04 -040077void
78UPnPContext::shutdown(std::condition_variable& cv)
79{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040080 if (logger_) logger_->debug("Shutdown UPnPContext instance [{}]", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -040081
82 stopUpnp(true);
83
84 for (auto const& [_, proto] : protocolList_) {
85 proto->terminate();
86 }
87
88 {
89 std::lock_guard<std::mutex> lock(mappingMutex_);
90 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040091 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040092 controllerList_.clear();
93 protocolList_.clear();
94 shutdownComplete_ = true;
95 cv.notify_one();
96 }
Adrien Béraudb04fbd72023-08-17 19:56:11 -040097
98 if (ioContextRunner_) {
99 if (logger_) logger_->debug("UPnPContext: stopping io_context thread");
100 ctx->stop();
101 ioContextRunner_->join();
102 ioContextRunner_.reset();
103 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400104}
105
106void
107UPnPContext::shutdown()
108{
109 std::unique_lock<std::mutex> lk(mappingMutex_);
110 std::condition_variable cv;
111
Adrien Béraud370257c2023-08-15 20:53:09 -0400112 ctx->post([&, this] { shutdown(cv); });
113
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400114 if (logger_) logger_->debug("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400115
116 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400117 if (logger_) logger_->debug("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118 } else {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400119 if (logger_) logger_->error("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400120 }
121}
122
123UPnPContext::~UPnPContext()
124{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400125 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400126}
127
128void
129UPnPContext::init()
130{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400131#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400132 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400133 natPmp->setObserver(this);
134 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
135#endif
136
137#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400138 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400139 pupnp->setObserver(this);
140 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
141#endif
142}
143
144void
145UPnPContext::startUpnp()
146{
147 assert(not controllerList_.empty());
148
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400149 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400150
151 // Request a new IGD search.
152 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400153 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400154 }
155
156 started_ = true;
157}
158
159void
160UPnPContext::stopUpnp(bool forceRelease)
161{
Adrien Béraud370257c2023-08-15 20:53:09 -0400162 /*if (not isValidThread()) {
163 ctx->post([this, forceRelease] { stopUpnp(forceRelease); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400164 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400165 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400166
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400167 if (logger_) logger_->debug("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400168
169 // Clear all current mappings if any.
170
171 // Use a temporary list to avoid processing the mapping
172 // list while holding the lock.
173 std::list<Mapping::sharedPtr_t> toRemoveList;
174 {
175 std::lock_guard<std::mutex> lock(mappingMutex_);
176
177 PortType types[2] {PortType::TCP, PortType::UDP};
178 for (auto& type : types) {
179 auto& mappingList = getMappingList(type);
180 for (auto const& [_, map] : mappingList) {
181 toRemoveList.emplace_back(map);
182 }
183 }
184 // Invalidate the current IGDs.
185 preferredIgd_.reset();
186 validIgdList_.clear();
187 }
188 for (auto const& map : toRemoveList) {
189 requestRemoveMapping(map);
190
Adrien Béraud370257c2023-08-15 20:53:09 -0400191 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400192 // shutting down (hence set it to false). NotifyCallback
193 // would trigger a new SIP registration and create a
194 // false registered state upon program close.
195 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400196 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400197 // We dont remove mappings with auto-update enabled,
198 // unless forceRelease is true.
199 if (not map->getAutoUpdate() or forceRelease) {
200 map->enableAutoUpdate(false);
201 unregisterMapping(map);
202 }
203 }
204
205 // Clear all current IGDs.
206 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400207 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400208 }
209
210 started_ = false;
211}
212
213uint16_t
214UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
215{
216 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
217 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
218
219 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400220 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400221 // Must be called with valid range.
222 assert(false);
223 }
224
225 int fact = mustBeEven ? 2 : 1;
226 if (mustBeEven) {
227 minPort /= fact;
228 maxPort /= fact;
229 }
230
231 // Seed the generator.
232 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
233 // Define the range.
234 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
235 return dist(gen) * fact;
236}
237
238void
239UPnPContext::connectivityChanged()
240{
Adrien Béraud370257c2023-08-15 20:53:09 -0400241 /*if (not isValidThread()) {
242 ctx->post([this] { connectivityChanged(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400243 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400244 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400245
246 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
247
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400248 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400249
250 auto restartUpnp = false;
251
252 // On reception of "connectivity change" notification, the UPNP search
253 // will be restarted if either there is no valid IGD, or the IGD address
254 // changed.
255
256 if (not isReady()) {
257 restartUpnp = true;
258 } else {
259 // Check if the host address changed.
260 for (auto const& [_, protocol] : protocolList_) {
261 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400262 if (logger_) logger_->warn("Host address changed from {} to {}",
263 protocol->getHostAddress().toString(),
264 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400265 protocol->clearIgds();
266 restartUpnp = true;
267 break;
268 }
269 }
270 }
271
272 // We have at least one valid IGD and the host address did
273 // not change, so no need to restart.
274 if (not restartUpnp) {
275 return;
276 }
277
278 // No registered controller. A new search will be performed when
279 // a controller is registered.
280 if (controllerList_.empty())
281 return;
282
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400283 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400284
285 stopUpnp();
286 startUpnp();
287
288 // Mapping with auto update enabled must be processed first.
289 processMappingWithAutoUpdate();
290}
291
292void
293UPnPContext::setPublicAddress(const IpAddr& addr)
294{
295 if (not addr)
296 return;
297
298 std::lock_guard<std::mutex> lock(mappingMutex_);
299 if (knownPublicAddress_ != addr) {
300 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400301 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400302 }
303}
304
305bool
306UPnPContext::isReady() const
307{
308 std::lock_guard<std::mutex> lock(mappingMutex_);
309 return not validIgdList_.empty();
310}
311
312IpAddr
313UPnPContext::getExternalIP() const
314{
315 std::lock_guard<std::mutex> lock(mappingMutex_);
316 // Return the first IGD Ip available.
317 if (not validIgdList_.empty()) {
318 return (*validIgdList_.begin())->getPublicIp();
319 }
320 return {};
321}
322
323Mapping::sharedPtr_t
324UPnPContext::reserveMapping(Mapping& requestedMap)
325{
326 auto desiredPort = requestedMap.getExternalPort();
327
328 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400329 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
330 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400331 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400332 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400333 }
334
335 Mapping::sharedPtr_t mapRes;
336
337 {
338 std::lock_guard<std::mutex> lock(mappingMutex_);
339 auto& mappingList = getMappingList(requestedMap.getType());
340
341 // We try to provide a mapping in "OPEN" state. If not found,
342 // we provide any available mapping. In this case, it's up to
343 // the caller to use it or not.
344 for (auto const& [_, map] : mappingList) {
345 // If the desired port is null, we pick the first available port.
346 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
347 and map->isAvailable()) {
348 // Considere the first available mapping regardless of its
349 // state. A mapping with OPEN state will be used if found.
350 if (not mapRes)
351 mapRes = map;
352
353 if (map->getState() == MappingState::OPEN) {
354 // Found an "OPEN" mapping. We are done.
355 mapRes = map;
356 break;
357 }
358 }
359 }
360 }
361
362 // Create a mapping if none was available.
363 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400364 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400365 mapRes = registerMapping(requestedMap);
366 }
367
368 if (mapRes) {
369 // Make the mapping unavailable
370 mapRes->setAvailable(false);
371 // Copy attributes.
372 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
373 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
374 // Notify the listener.
375 if (auto cb = mapRes->getNotifyCallback())
376 cb(mapRes);
377 }
378
379 updateMappingList(true);
380
381 return mapRes;
382}
383
384void
385UPnPContext::releaseMapping(const Mapping& map)
386{
Adrien Béraud370257c2023-08-15 20:53:09 -0400387 /*if (not isValidThread()) {
388 ctx->post([this, map] { releaseMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400389 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400390 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391
392 auto mapPtr = getMappingWithKey(map.getMapKey());
393
394 if (not mapPtr) {
395 // Might happen if the mapping failed or was never granted.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400396 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400397 return;
398 }
399
400 if (mapPtr->isAvailable()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400401 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400402 return;
403 }
404
405 // Remove it.
406 requestRemoveMapping(mapPtr);
407 unregisterMapping(mapPtr);
408}
409
410void
411UPnPContext::registerController(void* controller)
412{
413 {
414 std::lock_guard<std::mutex> lock(mappingMutex_);
415 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400416 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400417 return;
418 }
419 }
420
Adrien Béraud370257c2023-08-15 20:53:09 -0400421 /*if (not isValidThread()) {
422 ctx->post([this, controller] { registerController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400423 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400424 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400425
426 auto ret = controllerList_.emplace(controller);
427 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400428 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400429 return;
430 }
431
Adrien Berauda8731ac2023-08-17 12:19:39 -0400432 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400433 if (not started_)
434 startUpnp();
435}
436
437void
438UPnPContext::unregisterController(void* controller)
439{
Adrien Béraud370257c2023-08-15 20:53:09 -0400440 /*if (not isValidThread()) {
441 ctx->post([this, controller] { unregisterController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400442 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400443 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400444
445 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400446 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400447 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400448 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400449 }
450
451 if (controllerList_.empty()) {
452 stopUpnp();
453 }
454}
455
456uint16_t
457UPnPContext::getAvailablePortNumber(PortType type)
458{
459 // Only return an availalable random port. No actual
460 // reservation is made here.
461
462 std::lock_guard<std::mutex> lock(mappingMutex_);
463 auto& mappingList = getMappingList(type);
464 int tryCount = 0;
465 while (tryCount++ < MAX_REQUEST_RETRIES) {
466 uint16_t port = generateRandomPort(type);
467 Mapping map(type, port, port);
468 if (mappingList.find(map.getMapKey()) == mappingList.end())
469 return port;
470 }
471
472 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400473 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400474 return 0;
475}
476
477void
478UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
479{
480 assert(map);
481
Adrien Béraud370257c2023-08-15 20:53:09 -0400482 /*if (not isValidThread()) {
483 ctx->post([this, map] { requestMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400484 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400485 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400486
487 auto const& igd = getPreferredIgd();
488 // We must have at least a valid IGD pointer if we get here.
489 // Not this method is called only if there were a valid IGD, however,
490 // because the processing is asynchronous, it's possible that the IGD
491 // was invalidated when the this code executed.
492 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400493 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400494 return;
495 }
496
497 map->setIgd(igd);
498
Adrien Berauda8731ac2023-08-17 12:19:39 -0400499 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
500 map->toString(),
501 igd->getProtocolName(),
502 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400503
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400504 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400505
506 auto const& protocol = protocolList_.at(igd->getProtocol());
507 protocol->requestMappingAdd(*map);
508}
509
510bool
511UPnPContext::provisionNewMappings(PortType type, int portCount)
512{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400513 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400514
515 assert(portCount > 0);
516
517 while (portCount > 0) {
518 auto port = getAvailablePortNumber(type);
519 if (port > 0) {
520 // Found an available port number
521 portCount--;
522 Mapping map(type, port, port, true);
523 registerMapping(map);
524 } else {
525 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400526 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400527 return false;
528 }
529 }
530
531 return true;
532}
533
534bool
535UPnPContext::deleteUnneededMappings(PortType type, int portCount)
536{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400537 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400538
539 assert(portCount > 0);
540
Adrien Béraud370257c2023-08-15 20:53:09 -0400541 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400542
543 std::lock_guard<std::mutex> lock(mappingMutex_);
544 auto& mappingList = getMappingList(type);
545
546 for (auto it = mappingList.begin(); it != mappingList.end();) {
547 auto map = it->second;
548 assert(map);
549
550 if (not map->isAvailable()) {
551 it++;
552 continue;
553 }
554
555 if (map->getState() == MappingState::OPEN and portCount > 0) {
556 // Close portCount mappings in "OPEN" state.
557 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400558 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400559 portCount--;
560 } else if (map->getState() != MappingState::OPEN) {
561 // If this methods is called, it means there are more open
562 // mappings than required. So, all mappings in a state other
563 // than "OPEN" state (typically in in-progress state) will
564 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400565 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400566 } else {
567 it++;
568 }
569 }
570
571 return true;
572}
573
574void
575UPnPContext::updatePreferredIgd()
576{
Adrien Béraud370257c2023-08-15 20:53:09 -0400577 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400578
579 if (preferredIgd_ and preferredIgd_->isValid())
580 return;
581
582 // Reset and search for the best IGD.
583 preferredIgd_.reset();
584
585 for (auto const& [_, protocol] : protocolList_) {
586 if (protocol->isReady()) {
587 auto igdList = protocol->getIgdList();
588 assert(not igdList.empty());
589 auto const& igd = igdList.front();
590 if (not igd->isValid())
591 continue;
592
593 // Prefer NAT-PMP over PUPNP.
594 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
595 continue;
596
597 // Update.
598 preferredIgd_ = igd;
599 }
600 }
601
602 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400603 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
604 preferredIgd_->getProtocolName(),
605 preferredIgd_->getUID(),
606 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400607 }
608}
609
610std::shared_ptr<IGD>
611UPnPContext::getPreferredIgd() const
612{
Adrien Béraud370257c2023-08-15 20:53:09 -0400613 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400614
615 return preferredIgd_;
616}
617
618void
619UPnPContext::updateMappingList(bool async)
620{
621 // Run async if requested.
622 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400623 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400624 return;
625 }
626
Adrien Béraud370257c2023-08-15 20:53:09 -0400627 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400628
629 // Update the preferred IGD.
630 updatePreferredIgd();
631
Adrien Béraud25c30c42023-07-05 13:46:54 -0400632 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400633
634 // Skip if no controller registered.
635 if (controllerList_.empty())
636 return;
637
638 // Cancel the current timer (if any) and re-schedule.
639 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
640 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400641 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400642 // No valid IGD. Nothing to do.
643 return;
644 }
645
Adrien Berauda8731ac2023-08-17 12:19:39 -0400646 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400647 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
648 if (ec != asio::error::operation_aborted)
649 updateMappingList(false);
650 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400651
652 // Process pending requests if any.
653 processPendingRequests(prefIgd);
654
655 // Make new requests for mappings that failed and have
656 // the auto-update option enabled.
657 processMappingWithAutoUpdate();
658
659 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
660
661 for (auto idx : {0, 1}) {
662 auto type = typeArray[idx];
663
664 MappingStatus status;
665 getMappingStatus(type, status);
666
Adrien Berauda8731ac2023-08-17 12:19:39 -0400667 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
668 "in-progress, {:d} failed",
669 Mapping::getTypeStr(type),
670 status.sum(),
671 status.openCount_,
672 status.readyCount_,
673 status.openCount_ - status.readyCount_,
674 status.pendingCount_,
675 status.inProgressCount_,
676 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400677
678 if (status.failedCount_ > 0) {
679 std::lock_guard<std::mutex> lock(mappingMutex_);
680 auto const& mappingList = getMappingList(type);
681 for (auto const& [_, map] : mappingList) {
682 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400683 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
684 map->toString(true),
685 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400686 }
687 }
688 }
689
690 int toRequestCount = (int) minOpenPortLimit_[idx]
691 - (int) (status.readyCount_ + status.inProgressCount_
692 + status.pendingCount_);
693
694 // Provision/release mappings accordingly.
695 if (toRequestCount > 0) {
696 // Take into account the request in-progress when making
697 // requests for new mappings.
698 provisionNewMappings(type, toRequestCount);
699 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
700 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
701 }
702 }
703
704 // Prune the mapping list if needed
705 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
706#if HAVE_LIBNATPMP
707 // Dont perform if NAT-PMP is valid.
708 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
709#endif
710 {
711 pruneMappingList();
712 }
713 }
714
715#if HAVE_LIBNATPMP
716 // Renew nat-pmp allocations
717 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
718 renewAllocations();
719#endif
720}
721
722void
723UPnPContext::pruneMappingList()
724{
Adrien Béraud370257c2023-08-15 20:53:09 -0400725 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400726
727 MappingStatus status;
728 getMappingStatus(status);
729
730 // Do not prune the list if there are pending/in-progress requests.
731 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
732 return;
733 }
734
735 auto const& igd = getPreferredIgd();
736 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
737 return;
738 }
739 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
740
741 auto remoteMapList = protocol->getMappingsListByDescr(igd,
742 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
743 if (remoteMapList.empty()) {
744 std::lock_guard<std::mutex> lock(mappingMutex_);
745 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400746 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400747 }
748 }
749
750 pruneUnMatchedMappings(igd, remoteMapList);
751 pruneUnTrackedMappings(igd, remoteMapList);
752}
753
754void
755UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
756 const std::map<Mapping::key_t, Mapping>& remoteMapList)
757{
758 // Check/synchronize local mapping list with the list
759 // returned by the IGD.
760
761 PortType types[2] {PortType::TCP, PortType::UDP};
762
763 for (auto& type : types) {
764 // Use a temporary list to avoid processing mappings while holding the lock.
765 std::list<Mapping::sharedPtr_t> toRemoveList;
766 {
767 std::lock_guard<std::mutex> lock(mappingMutex_);
768 auto& mappingList = getMappingList(type);
769 for (auto const& [_, map] : mappingList) {
770 // Only check mappings allocated by UPNP protocol.
771 if (map->getProtocol() != NatProtocolType::PUPNP) {
772 continue;
773 }
774 // Set mapping as failed if not found in the list
775 // returned by the IGD.
776 if (map->getState() == MappingState::OPEN
777 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
778 toRemoveList.emplace_back(map);
779
Adrien Berauda8731ac2023-08-17 12:19:39 -0400780 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
781 "remote list. Mark as failed!",
782 map->toString(),
783 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400784 }
785 }
786 }
787
788 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400789 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400790 unregisterMapping(map);
791 }
792 }
793}
794
795void
796UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
797 const std::map<Mapping::key_t, Mapping>& remoteMapList)
798{
799 // Use a temporary list to avoid processing mappings while holding the lock.
800 std::list<Mapping> toRemoveList;
801 {
802 std::lock_guard<std::mutex> lock(mappingMutex_);
803
804 for (auto const& [_, map] : remoteMapList) {
805 // Must has valid IGD pointer and use UPNP protocol.
806 assert(map.getIgd());
807 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
808 auto& mappingList = getMappingList(map.getType());
809 auto it = mappingList.find(map.getMapKey());
810 if (it == mappingList.end()) {
811 // Not present, request mapping remove.
812 toRemoveList.emplace_back(std::move(map));
813 // Make only few remove requests at once.
814 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
815 break;
816 }
817 }
818 }
819
820 // Remove un-tracked mappings.
821 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
822 for (auto const& map : toRemoveList) {
823 protocol->requestMappingRemove(map);
824 }
825}
826
827void
828UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
829{
Adrien Béraud370257c2023-08-15 20:53:09 -0400830 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400831
832 // Use temporary list to avoid holding the lock while
833 // processing the mapping list.
834 std::list<Mapping::sharedPtr_t> toRemoveList;
835 {
836 std::lock_guard<std::mutex> lock(mappingMutex_);
837
838 PortType types[2] {PortType::TCP, PortType::UDP};
839 for (auto& type : types) {
840 auto& mappingList = getMappingList(type);
841 for (auto const& [_, map] : mappingList) {
842 if (map->getIgd() == igd)
843 toRemoveList.emplace_back(map);
844 }
845 }
846 }
847
848 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400849 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
850 map->toString(),
851 igd->toString(),
852 igd->getProtocolName());
Adrien Béraud370257c2023-08-15 20:53:09 -0400853 map->updateState(MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400854 unregisterMapping(map);
855 }
856}
857
858void
859UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
860{
861 // This list holds the mappings to be requested. This is
862 // needed to avoid performing the requests while holding
863 // the lock.
864 std::list<Mapping::sharedPtr_t> requestsList;
865
866 // Populate the list of requests to perform.
867 {
868 std::lock_guard<std::mutex> lock(mappingMutex_);
869 PortType typeArray[2] {PortType::TCP, PortType::UDP};
870
871 for (auto type : typeArray) {
872 auto& mappingList = getMappingList(type);
873 for (auto& [_, map] : mappingList) {
874 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400875 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
876 map->toString(),
877 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400878 requestsList.emplace_back(map);
879 }
880 }
881 }
882 }
883
884 // Process the pending requests.
885 for (auto const& map : requestsList) {
886 requestMapping(map);
887 }
888}
889
890void
891UPnPContext::processMappingWithAutoUpdate()
892{
893 // This list holds the mappings to be requested. This is
894 // needed to avoid performing the requests while holding
895 // the lock.
896 std::list<Mapping::sharedPtr_t> requestsList;
897
898 // Populate the list of requests for mappings with auto-update enabled.
899 {
900 std::lock_guard<std::mutex> lock(mappingMutex_);
901 PortType typeArray[2] {PortType::TCP, PortType::UDP};
902
903 for (auto type : typeArray) {
904 auto& mappingList = getMappingList(type);
905 for (auto const& [_, map] : mappingList) {
906 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
907 requestsList.emplace_back(map);
908 }
909 }
910 }
911 }
912
913 for (auto const& oldMap : requestsList) {
914 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400915 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
916 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400917
918 // Reserve a new mapping.
919 Mapping newMapping(oldMap->getType());
920 newMapping.enableAutoUpdate(true);
921 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
922
923 auto const& mapPtr = reserveMapping(newMapping);
924 assert(mapPtr);
925
926 // Release the old one.
927 oldMap->setAvailable(true);
928 oldMap->enableAutoUpdate(false);
929 oldMap->setNotifyCallback(nullptr);
930 unregisterMapping(oldMap);
931 }
932}
933
934void
935UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
936{
937 assert(igd);
938
Adrien Béraud370257c2023-08-15 20:53:09 -0400939 /*if (not isValidThread()) {
940 ctx->post([this, igd, event] { onIgdUpdated(igd, event); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400941 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400942 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400943
944 // Reset to start search for a new best IGD.
945 preferredIgd_.reset();
946
947 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
948 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
949 : "INVALID";
950
951 auto const& igdLocalAddr = igd->getLocalIp();
952 auto protocolName = igd->getProtocolName();
953
Adrien Berauda8731ac2023-08-17 12:19:39 -0400954 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
955 igd->getUID(),
956 igd->toString(),
957 protocolName,
958 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400959
960 // Check if the IGD has valid addresses.
961 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400962 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400963 return;
964 }
965
966 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400967 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400968 return;
969 }
970
971 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400972 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
973 " The mapped addresses might not be reachable",
974 protocolName,
975 igd->getPublicIp().toString(),
976 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400977 }
978
979 // The IGD was removed or is invalid.
980 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400981 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
982 igd->getUID(),
983 igd->toString(),
984 protocolName,
985 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400986
987 pruneMappingsWithInvalidIgds(igd);
988
989 std::lock_guard<std::mutex> lock(mappingMutex_);
990 validIgdList_.erase(igd);
991 return;
992 }
993
994 // Update the IGD list.
995 {
996 std::lock_guard<std::mutex> lock(mappingMutex_);
997 auto ret = validIgdList_.emplace(igd);
998 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400999 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
1000 protocolName,
1001 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001002 } else {
1003 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001004 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
1005 protocolName,
1006 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001007 return;
1008 }
1009 }
1010
1011 // Update the provisionned mappings.
1012 updateMappingList(false);
1013}
1014
1015void
1016UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1017{
Adrien Béraud370257c2023-08-15 20:53:09 -04001018 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001019
1020 // Check if we have a pending request for this response.
1021 auto map = getMappingWithKey(mapRes.getMapKey());
1022 if (not map) {
1023 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001024 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1025 mapRes.toString(),
1026 igd->toString(),
1027 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001028 return;
1029 }
1030
1031 // The mapping request is new and successful. Update.
1032 map->setIgd(igd);
1033 map->setInternalAddress(mapRes.getInternalAddress());
1034 map->setExternalPort(mapRes.getExternalPort());
1035
1036 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001037 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001038
Adrien Berauda8731ac2023-08-17 12:19:39 -04001039 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1040 map->toString(),
1041 igd->toString(),
1042 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001043
1044 // Call setValid() to reset the errors counter. We need
1045 // to reset the counter on each successful response.
1046 igd->setValid(true);
1047}
1048
1049#if HAVE_LIBNATPMP
1050void
1051UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1052{
1053 auto mapPtr = getMappingWithKey(map.getMapKey());
1054
1055 if (not mapPtr) {
1056 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001057 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1058 map.toString(),
1059 igd->toString(),
1060 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001061 return;
1062 }
1063 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1064 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001065 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1066 mapPtr->toString(),
1067 igd->toString(),
1068 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001069 return;
1070 }
1071
1072 mapPtr->setRenewalTime(map.getRenewalTime());
1073}
1074#endif
1075
1076void
1077UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1078{
Adrien Béraud370257c2023-08-15 20:53:09 -04001079 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001080 // Silently ignore if the mapping is invalid
1081 return;
1082 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001083 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1084 protocol->requestMappingRemove(*map);
1085}
1086
1087void
1088UPnPContext::deleteAllMappings(PortType type)
1089{
Adrien Béraud370257c2023-08-15 20:53:09 -04001090 /*if (not isValidThread()) {
1091 ctx->post([this, type] { deleteAllMappings(type); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001092 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001093 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001094
1095 std::lock_guard<std::mutex> lock(mappingMutex_);
1096 auto& mappingList = getMappingList(type);
1097
1098 for (auto const& [_, map] : mappingList) {
1099 requestRemoveMapping(map);
1100 }
1101}
1102
1103void
1104UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1105{
1106 if (not mapRes.isValid())
1107 return;
1108
Adrien Béraud370257c2023-08-15 20:53:09 -04001109 /*if (not isValidThread()) {
1110 ctx->post([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001111 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001112 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001113
1114 auto map = getMappingWithKey(mapRes.getMapKey());
1115 // Notify the listener.
1116 if (map and map->getNotifyCallback())
1117 map->getNotifyCallback()(map);
1118}
1119
1120Mapping::sharedPtr_t
1121UPnPContext::registerMapping(Mapping& map)
1122{
1123 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001124 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001125 auto port = getAvailablePortNumber(map.getType());
1126 map.setExternalPort(port);
1127 map.setInternalPort(port);
1128 }
1129
1130 // Newly added mapping must be in pending state by default.
1131 map.setState(MappingState::PENDING);
1132
1133 Mapping::sharedPtr_t mapPtr;
1134
1135 {
1136 std::lock_guard<std::mutex> lock(mappingMutex_);
1137 auto& mappingList = getMappingList(map.getType());
1138
1139 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1140 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001141 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001142 return {};
1143 }
1144 mapPtr = ret.first->second;
1145 assert(mapPtr);
1146 }
1147
1148 // No available IGD. The pending mapping requests will be processed
1149 // when a IGD becomes available (in onIgdAdded() method).
1150 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001151 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001152 } else {
1153 requestMapping(mapPtr);
1154 }
1155
1156 return mapPtr;
1157}
1158
Adrien Béraud612b55b2023-05-29 10:42:04 -04001159void
1160UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1161{
Adrien Béraud370257c2023-08-15 20:53:09 -04001162 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001163
1164 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001165 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001166 return;
1167 }
1168
1169 if (map->getAutoUpdate()) {
1170 // Dont unregister mappings with auto-update enabled.
1171 return;
1172 }
1173 auto& mappingList = getMappingList(map->getType());
1174
1175 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001176 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001177 } else {
1178 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001179 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1180 map->toString(),
1181 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001182 }
1183}
1184
1185std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1186UPnPContext::getMappingList(PortType type)
1187{
1188 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1189 return mappingList_[typeIdx];
1190}
1191
1192Mapping::sharedPtr_t
1193UPnPContext::getMappingWithKey(Mapping::key_t key)
1194{
1195 std::lock_guard<std::mutex> lock(mappingMutex_);
1196 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1197 auto it = mappingList.find(key);
1198 if (it == mappingList.end())
1199 return nullptr;
1200 return it->second;
1201}
1202
1203void
1204UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1205{
1206 std::lock_guard<std::mutex> lock(mappingMutex_);
1207 auto& mappingList = getMappingList(type);
1208
1209 for (auto const& [_, map] : mappingList) {
1210 switch (map->getState()) {
1211 case MappingState::PENDING: {
1212 status.pendingCount_++;
1213 break;
1214 }
1215 case MappingState::IN_PROGRESS: {
1216 status.inProgressCount_++;
1217 break;
1218 }
1219 case MappingState::FAILED: {
1220 status.failedCount_++;
1221 break;
1222 }
1223 case MappingState::OPEN: {
1224 status.openCount_++;
1225 if (map->isAvailable())
1226 status.readyCount_++;
1227 break;
1228 }
1229
1230 default:
1231 // Must not get here.
1232 assert(false);
1233 break;
1234 }
1235 }
1236}
1237
1238void
1239UPnPContext::getMappingStatus(MappingStatus& status)
1240{
1241 getMappingStatus(PortType::TCP, status);
1242 getMappingStatus(PortType::UDP, status);
1243}
1244
1245void
1246UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1247{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001248 auto const& map = getMappingWithKey(mapRes.getMapKey());
1249 if (not map) {
1250 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001251 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1252 mapRes.toString(),
1253 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001254 return;
1255 }
1256
1257 auto igd = map->getIgd();
1258 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001259 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001260 return;
1261 }
1262
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001263 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001264 unregisterMapping(map);
1265
Adrien Berauda8731ac2023-08-17 12:19:39 -04001266 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1267 map->toString(),
1268 igd->toString(),
1269 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001270}
1271
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001272void
1273UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1274{
1275 // CHECK_VALID_THREAD();
1276
1277 assert(map);
1278
1279 // Ignore if the state did not change.
1280 if (newState == map->getState()) {
1281 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1282 return;
1283 }
1284
1285 // Update the state.
1286 map->setState(newState);
1287
1288 // Notify the listener if set.
1289 if (notify and map->getNotifyCallback())
1290 map->getNotifyCallback()(map);
1291}
1292
Adrien Béraud612b55b2023-05-29 10:42:04 -04001293#if HAVE_LIBNATPMP
1294void
1295UPnPContext::renewAllocations()
1296{
Adrien Béraud370257c2023-08-15 20:53:09 -04001297 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001298
1299 // Check if the we have valid PMP IGD.
1300 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1301
1302 auto now = sys_clock::now();
1303 std::vector<Mapping::sharedPtr_t> toRenew;
1304
1305 for (auto type : {PortType::TCP, PortType::UDP}) {
1306 std::lock_guard<std::mutex> lock(mappingMutex_);
1307 auto mappingList = getMappingList(type);
1308 for (auto const& [_, map] : mappingList) {
1309 if (not map->isValid())
1310 continue;
1311 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1312 continue;
1313 if (map->getState() != MappingState::OPEN)
1314 continue;
1315 if (now < map->getRenewalTime())
1316 continue;
1317
1318 toRenew.emplace_back(map);
1319 }
1320 }
1321
1322 // Quit if there are no mapping to renew
1323 if (toRenew.empty())
1324 return;
1325
1326 for (auto const& map : toRenew) {
1327 pmpProto->requestMappingRenew(*map);
1328 }
1329}
1330#endif
1331
1332} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001333} // namespace dhtnet