blob: 09f441e1ab3f9539987b77df0cc744a9713851cf [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éraudc36965c2023-08-17 21:50:27 -040047 : ctx(createIoContext(ioContext, logger))
Adrien Béraud95219ef2023-08-17 21:55:37 -040048 , mappingListUpdateTimer_(*ctx)
49 , connectivityChangedTimer_(*ctx)
Adrien Béraudc36965c2023-08-17 21:50:27 -040050 , logger_(logger)
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
91 {
92 std::lock_guard<std::mutex> lock(mappingMutex_);
93 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040094 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040095 controllerList_.clear();
96 protocolList_.clear();
97 shutdownComplete_ = true;
98 cv.notify_one();
99 }
Adrien Béraudb04fbd72023-08-17 19:56:11 -0400100
101 if (ioContextRunner_) {
102 if (logger_) logger_->debug("UPnPContext: stopping io_context thread");
103 ctx->stop();
104 ioContextRunner_->join();
105 ioContextRunner_.reset();
106 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400107}
108
109void
110UPnPContext::shutdown()
111{
112 std::unique_lock<std::mutex> lk(mappingMutex_);
113 std::condition_variable cv;
114
Adrien Béraud370257c2023-08-15 20:53:09 -0400115 ctx->post([&, this] { shutdown(cv); });
116
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400117 if (logger_) logger_->debug("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118
119 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400120 if (logger_) logger_->debug("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400121 } else {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400122 if (logger_) logger_->error("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400123 }
124}
125
126UPnPContext::~UPnPContext()
127{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400128 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400129}
130
131void
132UPnPContext::init()
133{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400135 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400136 natPmp->setObserver(this);
137 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
138#endif
139
140#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400141 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400142 pupnp->setObserver(this);
143 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
144#endif
145}
146
147void
148UPnPContext::startUpnp()
149{
150 assert(not controllerList_.empty());
151
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400152 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400153
154 // Request a new IGD search.
155 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400156 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400157 }
158
159 started_ = true;
160}
161
162void
163UPnPContext::stopUpnp(bool forceRelease)
164{
Adrien Béraud370257c2023-08-15 20:53:09 -0400165 /*if (not isValidThread()) {
166 ctx->post([this, forceRelease] { stopUpnp(forceRelease); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400167 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400168 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400169
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400170 if (logger_) logger_->debug("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400171
172 // Clear all current mappings if any.
173
174 // Use a temporary list to avoid processing the mapping
175 // list while holding the lock.
176 std::list<Mapping::sharedPtr_t> toRemoveList;
177 {
178 std::lock_guard<std::mutex> lock(mappingMutex_);
179
180 PortType types[2] {PortType::TCP, PortType::UDP};
181 for (auto& type : types) {
182 auto& mappingList = getMappingList(type);
183 for (auto const& [_, map] : mappingList) {
184 toRemoveList.emplace_back(map);
185 }
186 }
187 // Invalidate the current IGDs.
188 preferredIgd_.reset();
189 validIgdList_.clear();
190 }
191 for (auto const& map : toRemoveList) {
192 requestRemoveMapping(map);
193
Adrien Béraud370257c2023-08-15 20:53:09 -0400194 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400195 // shutting down (hence set it to false). NotifyCallback
196 // would trigger a new SIP registration and create a
197 // false registered state upon program close.
198 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400199 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400200 // We dont remove mappings with auto-update enabled,
201 // unless forceRelease is true.
202 if (not map->getAutoUpdate() or forceRelease) {
203 map->enableAutoUpdate(false);
204 unregisterMapping(map);
205 }
206 }
207
208 // Clear all current IGDs.
209 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400210 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400211 }
212
213 started_ = false;
214}
215
216uint16_t
217UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
218{
219 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
220 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
221
222 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400223 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400224 // Must be called with valid range.
225 assert(false);
226 }
227
228 int fact = mustBeEven ? 2 : 1;
229 if (mustBeEven) {
230 minPort /= fact;
231 maxPort /= fact;
232 }
233
234 // Seed the generator.
235 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
236 // Define the range.
237 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
238 return dist(gen) * fact;
239}
240
241void
242UPnPContext::connectivityChanged()
243{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400244 // Debounce the connectivity change notification.
245 connectivityChangedTimer_.expires_after(std::chrono::milliseconds(50));
246 connectivityChangedTimer_.async_wait(std::bind(&UPnPContext::_connectivityChanged, this, std::placeholders::_1));
247}
248
249void
250UPnPContext::_connectivityChanged(const asio::error_code& ec)
251{
252 if (ec == asio::error::operation_aborted)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400253 return;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400254
255 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
256
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400257 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400258
259 auto restartUpnp = false;
260
261 // On reception of "connectivity change" notification, the UPNP search
262 // will be restarted if either there is no valid IGD, or the IGD address
263 // changed.
264
265 if (not isReady()) {
266 restartUpnp = true;
267 } else {
268 // Check if the host address changed.
269 for (auto const& [_, protocol] : protocolList_) {
270 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400271 if (logger_) logger_->warn("Host address changed from {} to {}",
272 protocol->getHostAddress().toString(),
273 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400274 protocol->clearIgds();
275 restartUpnp = true;
276 break;
277 }
278 }
279 }
280
281 // We have at least one valid IGD and the host address did
282 // not change, so no need to restart.
283 if (not restartUpnp) {
284 return;
285 }
286
287 // No registered controller. A new search will be performed when
288 // a controller is registered.
289 if (controllerList_.empty())
290 return;
291
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400292 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400293
294 stopUpnp();
295 startUpnp();
296
297 // Mapping with auto update enabled must be processed first.
298 processMappingWithAutoUpdate();
299}
300
301void
302UPnPContext::setPublicAddress(const IpAddr& addr)
303{
304 if (not addr)
305 return;
306
307 std::lock_guard<std::mutex> lock(mappingMutex_);
308 if (knownPublicAddress_ != addr) {
309 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400310 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400311 }
312}
313
314bool
315UPnPContext::isReady() const
316{
317 std::lock_guard<std::mutex> lock(mappingMutex_);
318 return not validIgdList_.empty();
319}
320
321IpAddr
322UPnPContext::getExternalIP() const
323{
324 std::lock_guard<std::mutex> lock(mappingMutex_);
325 // Return the first IGD Ip available.
326 if (not validIgdList_.empty()) {
327 return (*validIgdList_.begin())->getPublicIp();
328 }
329 return {};
330}
331
332Mapping::sharedPtr_t
333UPnPContext::reserveMapping(Mapping& requestedMap)
334{
335 auto desiredPort = requestedMap.getExternalPort();
336
337 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400338 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
339 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400340 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400341 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400342 }
343
344 Mapping::sharedPtr_t mapRes;
345
346 {
347 std::lock_guard<std::mutex> lock(mappingMutex_);
348 auto& mappingList = getMappingList(requestedMap.getType());
349
350 // We try to provide a mapping in "OPEN" state. If not found,
351 // we provide any available mapping. In this case, it's up to
352 // the caller to use it or not.
353 for (auto const& [_, map] : mappingList) {
354 // If the desired port is null, we pick the first available port.
355 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
356 and map->isAvailable()) {
357 // Considere the first available mapping regardless of its
358 // state. A mapping with OPEN state will be used if found.
359 if (not mapRes)
360 mapRes = map;
361
362 if (map->getState() == MappingState::OPEN) {
363 // Found an "OPEN" mapping. We are done.
364 mapRes = map;
365 break;
366 }
367 }
368 }
369 }
370
371 // Create a mapping if none was available.
372 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400373 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400374 mapRes = registerMapping(requestedMap);
375 }
376
377 if (mapRes) {
378 // Make the mapping unavailable
379 mapRes->setAvailable(false);
380 // Copy attributes.
381 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
382 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
383 // Notify the listener.
384 if (auto cb = mapRes->getNotifyCallback())
385 cb(mapRes);
386 }
387
388 updateMappingList(true);
389
390 return mapRes;
391}
392
393void
394UPnPContext::releaseMapping(const Mapping& map)
395{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400396 ctx->dispatch([this, map] {
397 auto mapPtr = getMappingWithKey(map.getMapKey());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400398
Adrien Béraudc36965c2023-08-17 21:50:27 -0400399 if (not mapPtr) {
400 // Might happen if the mapping failed or was never granted.
401 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
402 return;
403 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400404
Adrien Béraudc36965c2023-08-17 21:50:27 -0400405 if (mapPtr->isAvailable()) {
406 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
407 return;
408 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400409
Adrien Béraudc36965c2023-08-17 21:50:27 -0400410 // Remove it.
411 requestRemoveMapping(mapPtr);
412 unregisterMapping(mapPtr);
413 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414}
415
416void
417UPnPContext::registerController(void* controller)
418{
419 {
420 std::lock_guard<std::mutex> lock(mappingMutex_);
421 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400422 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400423 return;
424 }
Adrien Béraudc36965c2023-08-17 21:50:27 -0400425 auto ret = controllerList_.emplace(controller);
426 if (not ret.second) {
427 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
428 return;
429 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400430 }
431
Adrien Berauda8731ac2023-08-17 12:19:39 -0400432 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400433 if (not started_)
434 startUpnp();
435}
436
437void
438UPnPContext::unregisterController(void* controller)
439{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400440 std::unique_lock<std::mutex> lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400441 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400442 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400443 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400444 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400445 }
446
447 if (controllerList_.empty()) {
Adrien Béraudc36965c2023-08-17 21:50:27 -0400448 lock.unlock();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400449 stopUpnp();
450 }
451}
452
453uint16_t
454UPnPContext::getAvailablePortNumber(PortType type)
455{
456 // Only return an availalable random port. No actual
457 // reservation is made here.
458
459 std::lock_guard<std::mutex> lock(mappingMutex_);
460 auto& mappingList = getMappingList(type);
461 int tryCount = 0;
462 while (tryCount++ < MAX_REQUEST_RETRIES) {
463 uint16_t port = generateRandomPort(type);
464 Mapping map(type, port, port);
465 if (mappingList.find(map.getMapKey()) == mappingList.end())
466 return port;
467 }
468
469 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400470 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400471 return 0;
472}
473
474void
475UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
476{
477 assert(map);
478
Adrien Béraud370257c2023-08-15 20:53:09 -0400479 /*if (not isValidThread()) {
480 ctx->post([this, map] { requestMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400481 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400482 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400483
484 auto const& igd = getPreferredIgd();
485 // We must have at least a valid IGD pointer if we get here.
486 // Not this method is called only if there were a valid IGD, however,
487 // because the processing is asynchronous, it's possible that the IGD
488 // was invalidated when the this code executed.
489 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400490 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400491 return;
492 }
493
494 map->setIgd(igd);
495
Adrien Berauda8731ac2023-08-17 12:19:39 -0400496 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
497 map->toString(),
498 igd->getProtocolName(),
499 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400500
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400501 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400502
503 auto const& protocol = protocolList_.at(igd->getProtocol());
504 protocol->requestMappingAdd(*map);
505}
506
507bool
508UPnPContext::provisionNewMappings(PortType type, int portCount)
509{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400510 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400511
512 assert(portCount > 0);
513
514 while (portCount > 0) {
515 auto port = getAvailablePortNumber(type);
516 if (port > 0) {
517 // Found an available port number
518 portCount--;
519 Mapping map(type, port, port, true);
520 registerMapping(map);
521 } else {
522 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400523 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400524 return false;
525 }
526 }
527
528 return true;
529}
530
531bool
532UPnPContext::deleteUnneededMappings(PortType type, int portCount)
533{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400534 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400535
536 assert(portCount > 0);
537
Adrien Béraud370257c2023-08-15 20:53:09 -0400538 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400539
540 std::lock_guard<std::mutex> lock(mappingMutex_);
541 auto& mappingList = getMappingList(type);
542
543 for (auto it = mappingList.begin(); it != mappingList.end();) {
544 auto map = it->second;
545 assert(map);
546
547 if (not map->isAvailable()) {
548 it++;
549 continue;
550 }
551
552 if (map->getState() == MappingState::OPEN and portCount > 0) {
553 // Close portCount mappings in "OPEN" state.
554 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400555 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400556 portCount--;
557 } else if (map->getState() != MappingState::OPEN) {
558 // If this methods is called, it means there are more open
559 // mappings than required. So, all mappings in a state other
560 // than "OPEN" state (typically in in-progress state) will
561 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400562 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400563 } else {
564 it++;
565 }
566 }
567
568 return true;
569}
570
571void
572UPnPContext::updatePreferredIgd()
573{
Adrien Béraud370257c2023-08-15 20:53:09 -0400574 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400575
576 if (preferredIgd_ and preferredIgd_->isValid())
577 return;
578
579 // Reset and search for the best IGD.
580 preferredIgd_.reset();
581
582 for (auto const& [_, protocol] : protocolList_) {
583 if (protocol->isReady()) {
584 auto igdList = protocol->getIgdList();
585 assert(not igdList.empty());
586 auto const& igd = igdList.front();
587 if (not igd->isValid())
588 continue;
589
590 // Prefer NAT-PMP over PUPNP.
591 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
592 continue;
593
594 // Update.
595 preferredIgd_ = igd;
596 }
597 }
598
599 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400600 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
601 preferredIgd_->getProtocolName(),
602 preferredIgd_->getUID(),
603 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400604 }
605}
606
607std::shared_ptr<IGD>
608UPnPContext::getPreferredIgd() const
609{
Adrien Béraud370257c2023-08-15 20:53:09 -0400610 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400611
612 return preferredIgd_;
613}
614
615void
616UPnPContext::updateMappingList(bool async)
617{
618 // Run async if requested.
619 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400620 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400621 return;
622 }
623
Adrien Béraud370257c2023-08-15 20:53:09 -0400624 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400625
626 // Update the preferred IGD.
627 updatePreferredIgd();
628
Adrien Béraud25c30c42023-07-05 13:46:54 -0400629 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400630
631 // Skip if no controller registered.
632 if (controllerList_.empty())
633 return;
634
635 // Cancel the current timer (if any) and re-schedule.
636 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
637 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400638 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400639 // No valid IGD. Nothing to do.
640 return;
641 }
642
Adrien Berauda8731ac2023-08-17 12:19:39 -0400643 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400644 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
645 if (ec != asio::error::operation_aborted)
646 updateMappingList(false);
647 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400648
649 // Process pending requests if any.
650 processPendingRequests(prefIgd);
651
652 // Make new requests for mappings that failed and have
653 // the auto-update option enabled.
654 processMappingWithAutoUpdate();
655
656 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
657
658 for (auto idx : {0, 1}) {
659 auto type = typeArray[idx];
660
661 MappingStatus status;
662 getMappingStatus(type, status);
663
Adrien Berauda8731ac2023-08-17 12:19:39 -0400664 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
665 "in-progress, {:d} failed",
666 Mapping::getTypeStr(type),
667 status.sum(),
668 status.openCount_,
669 status.readyCount_,
670 status.openCount_ - status.readyCount_,
671 status.pendingCount_,
672 status.inProgressCount_,
673 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400674
675 if (status.failedCount_ > 0) {
676 std::lock_guard<std::mutex> lock(mappingMutex_);
677 auto const& mappingList = getMappingList(type);
678 for (auto const& [_, map] : mappingList) {
679 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400680 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
681 map->toString(true),
682 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400683 }
684 }
685 }
686
687 int toRequestCount = (int) minOpenPortLimit_[idx]
688 - (int) (status.readyCount_ + status.inProgressCount_
689 + status.pendingCount_);
690
691 // Provision/release mappings accordingly.
692 if (toRequestCount > 0) {
693 // Take into account the request in-progress when making
694 // requests for new mappings.
695 provisionNewMappings(type, toRequestCount);
696 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
697 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
698 }
699 }
700
701 // Prune the mapping list if needed
702 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
703#if HAVE_LIBNATPMP
704 // Dont perform if NAT-PMP is valid.
705 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
706#endif
707 {
708 pruneMappingList();
709 }
710 }
711
712#if HAVE_LIBNATPMP
713 // Renew nat-pmp allocations
714 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
715 renewAllocations();
716#endif
717}
718
719void
720UPnPContext::pruneMappingList()
721{
Adrien Béraud370257c2023-08-15 20:53:09 -0400722 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400723
724 MappingStatus status;
725 getMappingStatus(status);
726
727 // Do not prune the list if there are pending/in-progress requests.
728 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
729 return;
730 }
731
732 auto const& igd = getPreferredIgd();
733 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
734 return;
735 }
736 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
737
738 auto remoteMapList = protocol->getMappingsListByDescr(igd,
739 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Berauda0683d12023-08-22 18:09:02 -0400740 /*if (remoteMapList.empty()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400741 std::lock_guard<std::mutex> lock(mappingMutex_);
742 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400743 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400744 }
Adrien Berauda0683d12023-08-22 18:09:02 -0400745 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400746
747 pruneUnMatchedMappings(igd, remoteMapList);
748 pruneUnTrackedMappings(igd, remoteMapList);
749}
750
751void
752UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
753 const std::map<Mapping::key_t, Mapping>& remoteMapList)
754{
755 // Check/synchronize local mapping list with the list
756 // returned by the IGD.
757
Adrien Berauda0683d12023-08-22 18:09:02 -0400758 for (auto type: {PortType::TCP, PortType::UDP}) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400759 // Use a temporary list to avoid processing mappings while holding the lock.
760 std::list<Mapping::sharedPtr_t> toRemoveList;
761 {
762 std::lock_guard<std::mutex> lock(mappingMutex_);
Adrien Berauda0683d12023-08-22 18:09:02 -0400763 for (auto const& [_, map] : getMappingList(type)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400764 // Only check mappings allocated by UPNP protocol.
765 if (map->getProtocol() != NatProtocolType::PUPNP) {
766 continue;
767 }
768 // Set mapping as failed if not found in the list
769 // returned by the IGD.
770 if (map->getState() == MappingState::OPEN
771 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
772 toRemoveList.emplace_back(map);
773
Adrien Berauda8731ac2023-08-17 12:19:39 -0400774 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
775 "remote list. Mark as failed!",
776 map->toString(),
777 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400778 }
779 }
780 }
781
782 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400783 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400784 unregisterMapping(map);
785 }
786 }
787}
788
789void
790UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
791 const std::map<Mapping::key_t, Mapping>& remoteMapList)
792{
793 // Use a temporary list to avoid processing mappings while holding the lock.
794 std::list<Mapping> toRemoveList;
795 {
796 std::lock_guard<std::mutex> lock(mappingMutex_);
797
798 for (auto const& [_, map] : remoteMapList) {
799 // Must has valid IGD pointer and use UPNP protocol.
800 assert(map.getIgd());
801 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
802 auto& mappingList = getMappingList(map.getType());
803 auto it = mappingList.find(map.getMapKey());
804 if (it == mappingList.end()) {
805 // Not present, request mapping remove.
806 toRemoveList.emplace_back(std::move(map));
807 // Make only few remove requests at once.
808 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
809 break;
810 }
811 }
812 }
813
814 // Remove un-tracked mappings.
815 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
816 for (auto const& map : toRemoveList) {
817 protocol->requestMappingRemove(map);
818 }
819}
820
821void
822UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
823{
Adrien Béraud370257c2023-08-15 20:53:09 -0400824 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400825
826 // Use temporary list to avoid holding the lock while
827 // processing the mapping list.
828 std::list<Mapping::sharedPtr_t> toRemoveList;
829 {
830 std::lock_guard<std::mutex> lock(mappingMutex_);
831
832 PortType types[2] {PortType::TCP, PortType::UDP};
833 for (auto& type : types) {
834 auto& mappingList = getMappingList(type);
835 for (auto const& [_, map] : mappingList) {
836 if (map->getIgd() == igd)
837 toRemoveList.emplace_back(map);
838 }
839 }
840 }
841
842 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400843 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
844 map->toString(),
845 igd->toString(),
846 igd->getProtocolName());
Adrien Béraud56740312023-08-23 08:38:28 -0400847 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400848 unregisterMapping(map);
849 }
850}
851
852void
853UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
854{
855 // This list holds the mappings to be requested. This is
856 // needed to avoid performing the requests while holding
857 // the lock.
858 std::list<Mapping::sharedPtr_t> requestsList;
859
860 // Populate the list of requests to perform.
861 {
862 std::lock_guard<std::mutex> lock(mappingMutex_);
863 PortType typeArray[2] {PortType::TCP, PortType::UDP};
864
865 for (auto type : typeArray) {
866 auto& mappingList = getMappingList(type);
867 for (auto& [_, map] : mappingList) {
868 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400869 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
870 map->toString(),
871 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400872 requestsList.emplace_back(map);
873 }
874 }
875 }
876 }
877
878 // Process the pending requests.
879 for (auto const& map : requestsList) {
880 requestMapping(map);
881 }
882}
883
884void
885UPnPContext::processMappingWithAutoUpdate()
886{
887 // This list holds the mappings to be requested. This is
888 // needed to avoid performing the requests while holding
889 // the lock.
890 std::list<Mapping::sharedPtr_t> requestsList;
891
892 // Populate the list of requests for mappings with auto-update enabled.
893 {
894 std::lock_guard<std::mutex> lock(mappingMutex_);
895 PortType typeArray[2] {PortType::TCP, PortType::UDP};
896
897 for (auto type : typeArray) {
898 auto& mappingList = getMappingList(type);
899 for (auto const& [_, map] : mappingList) {
900 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
901 requestsList.emplace_back(map);
902 }
903 }
904 }
905 }
906
907 for (auto const& oldMap : requestsList) {
908 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400909 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
910 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400911
912 // Reserve a new mapping.
913 Mapping newMapping(oldMap->getType());
914 newMapping.enableAutoUpdate(true);
915 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
916
917 auto const& mapPtr = reserveMapping(newMapping);
918 assert(mapPtr);
919
920 // Release the old one.
921 oldMap->setAvailable(true);
922 oldMap->enableAutoUpdate(false);
923 oldMap->setNotifyCallback(nullptr);
924 unregisterMapping(oldMap);
925 }
926}
927
928void
929UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
930{
931 assert(igd);
932
Adrien Béraud370257c2023-08-15 20:53:09 -0400933 /*if (not isValidThread()) {
934 ctx->post([this, igd, event] { onIgdUpdated(igd, event); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400935 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400936 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400937
938 // Reset to start search for a new best IGD.
939 preferredIgd_.reset();
940
941 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
942 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
943 : "INVALID";
944
945 auto const& igdLocalAddr = igd->getLocalIp();
946 auto protocolName = igd->getProtocolName();
947
Adrien Berauda8731ac2023-08-17 12:19:39 -0400948 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
949 igd->getUID(),
950 igd->toString(),
951 protocolName,
952 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400953
954 // Check if the IGD has valid addresses.
955 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400956 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400957 return;
958 }
959
960 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400961 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400962 return;
963 }
964
965 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400966 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
967 " The mapped addresses might not be reachable",
968 protocolName,
969 igd->getPublicIp().toString(),
970 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400971 }
972
973 // The IGD was removed or is invalid.
974 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400975 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
976 igd->getUID(),
977 igd->toString(),
978 protocolName,
979 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400980
981 pruneMappingsWithInvalidIgds(igd);
982
983 std::lock_guard<std::mutex> lock(mappingMutex_);
984 validIgdList_.erase(igd);
985 return;
986 }
987
988 // Update the IGD list.
989 {
990 std::lock_guard<std::mutex> lock(mappingMutex_);
991 auto ret = validIgdList_.emplace(igd);
992 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400993 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
994 protocolName,
995 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400996 } else {
997 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400998 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
999 protocolName,
1000 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001001 return;
1002 }
1003 }
1004
1005 // Update the provisionned mappings.
1006 updateMappingList(false);
1007}
1008
1009void
1010UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1011{
Adrien Béraud370257c2023-08-15 20:53:09 -04001012 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001013
1014 // Check if we have a pending request for this response.
1015 auto map = getMappingWithKey(mapRes.getMapKey());
1016 if (not map) {
1017 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001018 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1019 mapRes.toString(),
1020 igd->toString(),
1021 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001022 return;
1023 }
1024
1025 // The mapping request is new and successful. Update.
1026 map->setIgd(igd);
1027 map->setInternalAddress(mapRes.getInternalAddress());
1028 map->setExternalPort(mapRes.getExternalPort());
1029
1030 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001031 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001032
Adrien Berauda8731ac2023-08-17 12:19:39 -04001033 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1034 map->toString(),
1035 igd->toString(),
1036 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001037
1038 // Call setValid() to reset the errors counter. We need
1039 // to reset the counter on each successful response.
1040 igd->setValid(true);
1041}
1042
1043#if HAVE_LIBNATPMP
1044void
1045UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1046{
1047 auto mapPtr = getMappingWithKey(map.getMapKey());
1048
1049 if (not mapPtr) {
1050 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001051 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1052 map.toString(),
1053 igd->toString(),
1054 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001055 return;
1056 }
1057 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1058 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001059 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1060 mapPtr->toString(),
1061 igd->toString(),
1062 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001063 return;
1064 }
1065
1066 mapPtr->setRenewalTime(map.getRenewalTime());
1067}
1068#endif
1069
1070void
1071UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1072{
Adrien Béraud370257c2023-08-15 20:53:09 -04001073 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001074 // Silently ignore if the mapping is invalid
1075 return;
1076 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001077 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1078 protocol->requestMappingRemove(*map);
1079}
1080
1081void
1082UPnPContext::deleteAllMappings(PortType type)
1083{
Adrien Béraud370257c2023-08-15 20:53:09 -04001084 /*if (not isValidThread()) {
1085 ctx->post([this, type] { deleteAllMappings(type); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001086 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001087 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001088
1089 std::lock_guard<std::mutex> lock(mappingMutex_);
1090 auto& mappingList = getMappingList(type);
1091
1092 for (auto const& [_, map] : mappingList) {
1093 requestRemoveMapping(map);
1094 }
1095}
1096
1097void
1098UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1099{
1100 if (not mapRes.isValid())
1101 return;
1102
Adrien Béraud370257c2023-08-15 20:53:09 -04001103 /*if (not isValidThread()) {
1104 ctx->post([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001105 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001106 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001107
1108 auto map = getMappingWithKey(mapRes.getMapKey());
1109 // Notify the listener.
1110 if (map and map->getNotifyCallback())
1111 map->getNotifyCallback()(map);
1112}
1113
1114Mapping::sharedPtr_t
1115UPnPContext::registerMapping(Mapping& map)
1116{
1117 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001118 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001119 auto port = getAvailablePortNumber(map.getType());
1120 map.setExternalPort(port);
1121 map.setInternalPort(port);
1122 }
1123
1124 // Newly added mapping must be in pending state by default.
1125 map.setState(MappingState::PENDING);
1126
1127 Mapping::sharedPtr_t mapPtr;
1128
1129 {
1130 std::lock_guard<std::mutex> lock(mappingMutex_);
1131 auto& mappingList = getMappingList(map.getType());
1132
1133 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1134 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001135 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001136 return {};
1137 }
1138 mapPtr = ret.first->second;
1139 assert(mapPtr);
1140 }
1141
1142 // No available IGD. The pending mapping requests will be processed
1143 // when a IGD becomes available (in onIgdAdded() method).
1144 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001145 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001146 } else {
1147 requestMapping(mapPtr);
1148 }
1149
1150 return mapPtr;
1151}
1152
Adrien Béraud612b55b2023-05-29 10:42:04 -04001153void
1154UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1155{
Adrien Béraud370257c2023-08-15 20:53:09 -04001156 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001157
1158 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001159 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001160 return;
1161 }
1162
1163 if (map->getAutoUpdate()) {
1164 // Dont unregister mappings with auto-update enabled.
1165 return;
1166 }
1167 auto& mappingList = getMappingList(map->getType());
1168
1169 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001170 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001171 } else {
1172 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001173 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1174 map->toString(),
1175 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001176 }
1177}
1178
1179std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1180UPnPContext::getMappingList(PortType type)
1181{
1182 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1183 return mappingList_[typeIdx];
1184}
1185
1186Mapping::sharedPtr_t
1187UPnPContext::getMappingWithKey(Mapping::key_t key)
1188{
1189 std::lock_guard<std::mutex> lock(mappingMutex_);
1190 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1191 auto it = mappingList.find(key);
1192 if (it == mappingList.end())
1193 return nullptr;
1194 return it->second;
1195}
1196
1197void
1198UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1199{
1200 std::lock_guard<std::mutex> lock(mappingMutex_);
1201 auto& mappingList = getMappingList(type);
1202
1203 for (auto const& [_, map] : mappingList) {
1204 switch (map->getState()) {
1205 case MappingState::PENDING: {
1206 status.pendingCount_++;
1207 break;
1208 }
1209 case MappingState::IN_PROGRESS: {
1210 status.inProgressCount_++;
1211 break;
1212 }
1213 case MappingState::FAILED: {
1214 status.failedCount_++;
1215 break;
1216 }
1217 case MappingState::OPEN: {
1218 status.openCount_++;
1219 if (map->isAvailable())
1220 status.readyCount_++;
1221 break;
1222 }
1223
1224 default:
1225 // Must not get here.
1226 assert(false);
1227 break;
1228 }
1229 }
1230}
1231
1232void
1233UPnPContext::getMappingStatus(MappingStatus& status)
1234{
1235 getMappingStatus(PortType::TCP, status);
1236 getMappingStatus(PortType::UDP, status);
1237}
1238
1239void
1240UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1241{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001242 auto const& map = getMappingWithKey(mapRes.getMapKey());
1243 if (not map) {
1244 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001245 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1246 mapRes.toString(),
1247 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001248 return;
1249 }
1250
1251 auto igd = map->getIgd();
1252 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001253 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001254 return;
1255 }
1256
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001257 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001258 unregisterMapping(map);
1259
Adrien Berauda8731ac2023-08-17 12:19:39 -04001260 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1261 map->toString(),
1262 igd->toString(),
1263 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001264}
1265
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001266void
1267UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1268{
1269 // CHECK_VALID_THREAD();
1270
1271 assert(map);
1272
1273 // Ignore if the state did not change.
1274 if (newState == map->getState()) {
1275 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1276 return;
1277 }
1278
1279 // Update the state.
1280 map->setState(newState);
1281
1282 // Notify the listener if set.
1283 if (notify and map->getNotifyCallback())
1284 map->getNotifyCallback()(map);
1285}
1286
Adrien Béraud612b55b2023-05-29 10:42:04 -04001287#if HAVE_LIBNATPMP
1288void
1289UPnPContext::renewAllocations()
1290{
Adrien Béraud370257c2023-08-15 20:53:09 -04001291 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001292
1293 // Check if the we have valid PMP IGD.
1294 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1295
1296 auto now = sys_clock::now();
1297 std::vector<Mapping::sharedPtr_t> toRenew;
1298
1299 for (auto type : {PortType::TCP, PortType::UDP}) {
1300 std::lock_guard<std::mutex> lock(mappingMutex_);
1301 auto mappingList = getMappingList(type);
1302 for (auto const& [_, map] : mappingList) {
1303 if (not map->isValid())
1304 continue;
1305 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1306 continue;
1307 if (map->getState() != MappingState::OPEN)
1308 continue;
1309 if (now < map->getRenewalTime())
1310 continue;
1311
1312 toRenew.emplace_back(map);
1313 }
1314 }
1315
1316 // Quit if there are no mapping to renew
1317 if (toRenew.empty())
1318 return;
1319
1320 for (auto const& map : toRenew) {
1321 pmpProto->requestMappingRenew(*map);
1322 }
1323}
1324#endif
1325
1326} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001327} // namespace dhtnet