blob: 335f70ba199d55fd14cc83fcc60ce82b0638efff [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éraud370257c2023-08-15 20:53:09 -040047 : mappingListUpdateTimer_(*ioContext), ctx(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éraud612b55b2023-05-29 10:42:04 -040058void
59UPnPContext::shutdown(std::condition_variable& cv)
60{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040061 if (logger_) logger_->debug("Shutdown UPnPContext instance [{}]", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -040062
63 stopUpnp(true);
64
65 for (auto const& [_, proto] : protocolList_) {
66 proto->terminate();
67 }
68
69 {
70 std::lock_guard<std::mutex> lock(mappingMutex_);
71 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040072 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040073 controllerList_.clear();
74 protocolList_.clear();
75 shutdownComplete_ = true;
76 cv.notify_one();
77 }
78}
79
80void
81UPnPContext::shutdown()
82{
83 std::unique_lock<std::mutex> lk(mappingMutex_);
84 std::condition_variable cv;
85
Adrien Béraud370257c2023-08-15 20:53:09 -040086 ctx->post([&, this] { shutdown(cv); });
87
Adrien Beraud3bd61c92023-08-17 16:57:37 -040088 if (logger_) logger_->debug("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -040089
90 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -040091 if (logger_) logger_->debug("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -040092 } else {
Adrien Beraud3bd61c92023-08-17 16:57:37 -040093 if (logger_) logger_->error("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -040094 }
95}
96
97UPnPContext::~UPnPContext()
98{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040099 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400100}
101
102void
103UPnPContext::init()
104{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400105#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400106 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400107 natPmp->setObserver(this);
108 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
109#endif
110
111#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400112 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400113 pupnp->setObserver(this);
114 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
115#endif
116}
117
118void
119UPnPContext::startUpnp()
120{
121 assert(not controllerList_.empty());
122
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400123 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400124
125 // Request a new IGD search.
126 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400127 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400128 }
129
130 started_ = true;
131}
132
133void
134UPnPContext::stopUpnp(bool forceRelease)
135{
Adrien Béraud370257c2023-08-15 20:53:09 -0400136 /*if (not isValidThread()) {
137 ctx->post([this, forceRelease] { stopUpnp(forceRelease); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400138 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400139 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400140
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400141 if (logger_) logger_->debug("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400142
143 // Clear all current mappings if any.
144
145 // Use a temporary list to avoid processing the mapping
146 // list while holding the lock.
147 std::list<Mapping::sharedPtr_t> toRemoveList;
148 {
149 std::lock_guard<std::mutex> lock(mappingMutex_);
150
151 PortType types[2] {PortType::TCP, PortType::UDP};
152 for (auto& type : types) {
153 auto& mappingList = getMappingList(type);
154 for (auto const& [_, map] : mappingList) {
155 toRemoveList.emplace_back(map);
156 }
157 }
158 // Invalidate the current IGDs.
159 preferredIgd_.reset();
160 validIgdList_.clear();
161 }
162 for (auto const& map : toRemoveList) {
163 requestRemoveMapping(map);
164
Adrien Béraud370257c2023-08-15 20:53:09 -0400165 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400166 // shutting down (hence set it to false). NotifyCallback
167 // would trigger a new SIP registration and create a
168 // false registered state upon program close.
169 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400170 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400171 // We dont remove mappings with auto-update enabled,
172 // unless forceRelease is true.
173 if (not map->getAutoUpdate() or forceRelease) {
174 map->enableAutoUpdate(false);
175 unregisterMapping(map);
176 }
177 }
178
179 // Clear all current IGDs.
180 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400181 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400182 }
183
184 started_ = false;
185}
186
187uint16_t
188UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
189{
190 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
191 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
192
193 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400194 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400195 // Must be called with valid range.
196 assert(false);
197 }
198
199 int fact = mustBeEven ? 2 : 1;
200 if (mustBeEven) {
201 minPort /= fact;
202 maxPort /= fact;
203 }
204
205 // Seed the generator.
206 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
207 // Define the range.
208 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
209 return dist(gen) * fact;
210}
211
212void
213UPnPContext::connectivityChanged()
214{
Adrien Béraud370257c2023-08-15 20:53:09 -0400215 /*if (not isValidThread()) {
216 ctx->post([this] { connectivityChanged(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400217 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400218 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400219
220 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
221
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400222 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400223
224 auto restartUpnp = false;
225
226 // On reception of "connectivity change" notification, the UPNP search
227 // will be restarted if either there is no valid IGD, or the IGD address
228 // changed.
229
230 if (not isReady()) {
231 restartUpnp = true;
232 } else {
233 // Check if the host address changed.
234 for (auto const& [_, protocol] : protocolList_) {
235 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400236 if (logger_) logger_->warn("Host address changed from {} to {}",
237 protocol->getHostAddress().toString(),
238 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400239 protocol->clearIgds();
240 restartUpnp = true;
241 break;
242 }
243 }
244 }
245
246 // We have at least one valid IGD and the host address did
247 // not change, so no need to restart.
248 if (not restartUpnp) {
249 return;
250 }
251
252 // No registered controller. A new search will be performed when
253 // a controller is registered.
254 if (controllerList_.empty())
255 return;
256
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400257 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400258
259 stopUpnp();
260 startUpnp();
261
262 // Mapping with auto update enabled must be processed first.
263 processMappingWithAutoUpdate();
264}
265
266void
267UPnPContext::setPublicAddress(const IpAddr& addr)
268{
269 if (not addr)
270 return;
271
272 std::lock_guard<std::mutex> lock(mappingMutex_);
273 if (knownPublicAddress_ != addr) {
274 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400275 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400276 }
277}
278
279bool
280UPnPContext::isReady() const
281{
282 std::lock_guard<std::mutex> lock(mappingMutex_);
283 return not validIgdList_.empty();
284}
285
286IpAddr
287UPnPContext::getExternalIP() const
288{
289 std::lock_guard<std::mutex> lock(mappingMutex_);
290 // Return the first IGD Ip available.
291 if (not validIgdList_.empty()) {
292 return (*validIgdList_.begin())->getPublicIp();
293 }
294 return {};
295}
296
297Mapping::sharedPtr_t
298UPnPContext::reserveMapping(Mapping& requestedMap)
299{
300 auto desiredPort = requestedMap.getExternalPort();
301
302 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400303 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
304 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400305 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400306 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400307 }
308
309 Mapping::sharedPtr_t mapRes;
310
311 {
312 std::lock_guard<std::mutex> lock(mappingMutex_);
313 auto& mappingList = getMappingList(requestedMap.getType());
314
315 // We try to provide a mapping in "OPEN" state. If not found,
316 // we provide any available mapping. In this case, it's up to
317 // the caller to use it or not.
318 for (auto const& [_, map] : mappingList) {
319 // If the desired port is null, we pick the first available port.
320 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
321 and map->isAvailable()) {
322 // Considere the first available mapping regardless of its
323 // state. A mapping with OPEN state will be used if found.
324 if (not mapRes)
325 mapRes = map;
326
327 if (map->getState() == MappingState::OPEN) {
328 // Found an "OPEN" mapping. We are done.
329 mapRes = map;
330 break;
331 }
332 }
333 }
334 }
335
336 // Create a mapping if none was available.
337 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400338 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400339 mapRes = registerMapping(requestedMap);
340 }
341
342 if (mapRes) {
343 // Make the mapping unavailable
344 mapRes->setAvailable(false);
345 // Copy attributes.
346 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
347 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
348 // Notify the listener.
349 if (auto cb = mapRes->getNotifyCallback())
350 cb(mapRes);
351 }
352
353 updateMappingList(true);
354
355 return mapRes;
356}
357
358void
359UPnPContext::releaseMapping(const Mapping& map)
360{
Adrien Béraud370257c2023-08-15 20:53:09 -0400361 /*if (not isValidThread()) {
362 ctx->post([this, map] { releaseMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400363 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400364 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400365
366 auto mapPtr = getMappingWithKey(map.getMapKey());
367
368 if (not mapPtr) {
369 // Might happen if the mapping failed or was never granted.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400370 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400371 return;
372 }
373
374 if (mapPtr->isAvailable()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400375 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400376 return;
377 }
378
379 // Remove it.
380 requestRemoveMapping(mapPtr);
381 unregisterMapping(mapPtr);
382}
383
384void
385UPnPContext::registerController(void* controller)
386{
387 {
388 std::lock_guard<std::mutex> lock(mappingMutex_);
389 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400390 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391 return;
392 }
393 }
394
Adrien Béraud370257c2023-08-15 20:53:09 -0400395 /*if (not isValidThread()) {
396 ctx->post([this, controller] { registerController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400397 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400398 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400399
400 auto ret = controllerList_.emplace(controller);
401 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400402 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400403 return;
404 }
405
Adrien Berauda8731ac2023-08-17 12:19:39 -0400406 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400407 if (not started_)
408 startUpnp();
409}
410
411void
412UPnPContext::unregisterController(void* controller)
413{
Adrien Béraud370257c2023-08-15 20:53:09 -0400414 /*if (not isValidThread()) {
415 ctx->post([this, controller] { unregisterController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400416 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400417 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400418
419 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400420 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400421 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400422 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400423 }
424
425 if (controllerList_.empty()) {
426 stopUpnp();
427 }
428}
429
430uint16_t
431UPnPContext::getAvailablePortNumber(PortType type)
432{
433 // Only return an availalable random port. No actual
434 // reservation is made here.
435
436 std::lock_guard<std::mutex> lock(mappingMutex_);
437 auto& mappingList = getMappingList(type);
438 int tryCount = 0;
439 while (tryCount++ < MAX_REQUEST_RETRIES) {
440 uint16_t port = generateRandomPort(type);
441 Mapping map(type, port, port);
442 if (mappingList.find(map.getMapKey()) == mappingList.end())
443 return port;
444 }
445
446 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400447 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400448 return 0;
449}
450
451void
452UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
453{
454 assert(map);
455
Adrien Béraud370257c2023-08-15 20:53:09 -0400456 /*if (not isValidThread()) {
457 ctx->post([this, map] { requestMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400458 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400459 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400460
461 auto const& igd = getPreferredIgd();
462 // We must have at least a valid IGD pointer if we get here.
463 // Not this method is called only if there were a valid IGD, however,
464 // because the processing is asynchronous, it's possible that the IGD
465 // was invalidated when the this code executed.
466 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400467 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400468 return;
469 }
470
471 map->setIgd(igd);
472
Adrien Berauda8731ac2023-08-17 12:19:39 -0400473 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
474 map->toString(),
475 igd->getProtocolName(),
476 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400477
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400478 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400479
480 auto const& protocol = protocolList_.at(igd->getProtocol());
481 protocol->requestMappingAdd(*map);
482}
483
484bool
485UPnPContext::provisionNewMappings(PortType type, int portCount)
486{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400487 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400488
489 assert(portCount > 0);
490
491 while (portCount > 0) {
492 auto port = getAvailablePortNumber(type);
493 if (port > 0) {
494 // Found an available port number
495 portCount--;
496 Mapping map(type, port, port, true);
497 registerMapping(map);
498 } else {
499 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400500 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400501 return false;
502 }
503 }
504
505 return true;
506}
507
508bool
509UPnPContext::deleteUnneededMappings(PortType type, int portCount)
510{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400511 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400512
513 assert(portCount > 0);
514
Adrien Béraud370257c2023-08-15 20:53:09 -0400515 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400516
517 std::lock_guard<std::mutex> lock(mappingMutex_);
518 auto& mappingList = getMappingList(type);
519
520 for (auto it = mappingList.begin(); it != mappingList.end();) {
521 auto map = it->second;
522 assert(map);
523
524 if (not map->isAvailable()) {
525 it++;
526 continue;
527 }
528
529 if (map->getState() == MappingState::OPEN and portCount > 0) {
530 // Close portCount mappings in "OPEN" state.
531 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400532 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400533 portCount--;
534 } else if (map->getState() != MappingState::OPEN) {
535 // If this methods is called, it means there are more open
536 // mappings than required. So, all mappings in a state other
537 // than "OPEN" state (typically in in-progress state) will
538 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400539 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400540 } else {
541 it++;
542 }
543 }
544
545 return true;
546}
547
548void
549UPnPContext::updatePreferredIgd()
550{
Adrien Béraud370257c2023-08-15 20:53:09 -0400551 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400552
553 if (preferredIgd_ and preferredIgd_->isValid())
554 return;
555
556 // Reset and search for the best IGD.
557 preferredIgd_.reset();
558
559 for (auto const& [_, protocol] : protocolList_) {
560 if (protocol->isReady()) {
561 auto igdList = protocol->getIgdList();
562 assert(not igdList.empty());
563 auto const& igd = igdList.front();
564 if (not igd->isValid())
565 continue;
566
567 // Prefer NAT-PMP over PUPNP.
568 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
569 continue;
570
571 // Update.
572 preferredIgd_ = igd;
573 }
574 }
575
576 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400577 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
578 preferredIgd_->getProtocolName(),
579 preferredIgd_->getUID(),
580 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400581 }
582}
583
584std::shared_ptr<IGD>
585UPnPContext::getPreferredIgd() const
586{
Adrien Béraud370257c2023-08-15 20:53:09 -0400587 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400588
589 return preferredIgd_;
590}
591
592void
593UPnPContext::updateMappingList(bool async)
594{
595 // Run async if requested.
596 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400597 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400598 return;
599 }
600
Adrien Béraud370257c2023-08-15 20:53:09 -0400601 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400602
603 // Update the preferred IGD.
604 updatePreferredIgd();
605
Adrien Béraud25c30c42023-07-05 13:46:54 -0400606 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400607
608 // Skip if no controller registered.
609 if (controllerList_.empty())
610 return;
611
612 // Cancel the current timer (if any) and re-schedule.
613 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
614 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400615 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400616 // No valid IGD. Nothing to do.
617 return;
618 }
619
Adrien Berauda8731ac2023-08-17 12:19:39 -0400620 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400621 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
622 if (ec != asio::error::operation_aborted)
623 updateMappingList(false);
624 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400625
626 // Process pending requests if any.
627 processPendingRequests(prefIgd);
628
629 // Make new requests for mappings that failed and have
630 // the auto-update option enabled.
631 processMappingWithAutoUpdate();
632
633 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
634
635 for (auto idx : {0, 1}) {
636 auto type = typeArray[idx];
637
638 MappingStatus status;
639 getMappingStatus(type, status);
640
Adrien Berauda8731ac2023-08-17 12:19:39 -0400641 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
642 "in-progress, {:d} failed",
643 Mapping::getTypeStr(type),
644 status.sum(),
645 status.openCount_,
646 status.readyCount_,
647 status.openCount_ - status.readyCount_,
648 status.pendingCount_,
649 status.inProgressCount_,
650 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400651
652 if (status.failedCount_ > 0) {
653 std::lock_guard<std::mutex> lock(mappingMutex_);
654 auto const& mappingList = getMappingList(type);
655 for (auto const& [_, map] : mappingList) {
656 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400657 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
658 map->toString(true),
659 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400660 }
661 }
662 }
663
664 int toRequestCount = (int) minOpenPortLimit_[idx]
665 - (int) (status.readyCount_ + status.inProgressCount_
666 + status.pendingCount_);
667
668 // Provision/release mappings accordingly.
669 if (toRequestCount > 0) {
670 // Take into account the request in-progress when making
671 // requests for new mappings.
672 provisionNewMappings(type, toRequestCount);
673 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
674 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
675 }
676 }
677
678 // Prune the mapping list if needed
679 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
680#if HAVE_LIBNATPMP
681 // Dont perform if NAT-PMP is valid.
682 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
683#endif
684 {
685 pruneMappingList();
686 }
687 }
688
689#if HAVE_LIBNATPMP
690 // Renew nat-pmp allocations
691 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
692 renewAllocations();
693#endif
694}
695
696void
697UPnPContext::pruneMappingList()
698{
Adrien Béraud370257c2023-08-15 20:53:09 -0400699 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400700
701 MappingStatus status;
702 getMappingStatus(status);
703
704 // Do not prune the list if there are pending/in-progress requests.
705 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
706 return;
707 }
708
709 auto const& igd = getPreferredIgd();
710 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
711 return;
712 }
713 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
714
715 auto remoteMapList = protocol->getMappingsListByDescr(igd,
716 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
717 if (remoteMapList.empty()) {
718 std::lock_guard<std::mutex> lock(mappingMutex_);
719 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400720 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400721 }
722 }
723
724 pruneUnMatchedMappings(igd, remoteMapList);
725 pruneUnTrackedMappings(igd, remoteMapList);
726}
727
728void
729UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
730 const std::map<Mapping::key_t, Mapping>& remoteMapList)
731{
732 // Check/synchronize local mapping list with the list
733 // returned by the IGD.
734
735 PortType types[2] {PortType::TCP, PortType::UDP};
736
737 for (auto& type : types) {
738 // Use a temporary list to avoid processing mappings while holding the lock.
739 std::list<Mapping::sharedPtr_t> toRemoveList;
740 {
741 std::lock_guard<std::mutex> lock(mappingMutex_);
742 auto& mappingList = getMappingList(type);
743 for (auto const& [_, map] : mappingList) {
744 // Only check mappings allocated by UPNP protocol.
745 if (map->getProtocol() != NatProtocolType::PUPNP) {
746 continue;
747 }
748 // Set mapping as failed if not found in the list
749 // returned by the IGD.
750 if (map->getState() == MappingState::OPEN
751 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
752 toRemoveList.emplace_back(map);
753
Adrien Berauda8731ac2023-08-17 12:19:39 -0400754 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
755 "remote list. Mark as failed!",
756 map->toString(),
757 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400758 }
759 }
760 }
761
762 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400763 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400764 unregisterMapping(map);
765 }
766 }
767}
768
769void
770UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
771 const std::map<Mapping::key_t, Mapping>& remoteMapList)
772{
773 // Use a temporary list to avoid processing mappings while holding the lock.
774 std::list<Mapping> toRemoveList;
775 {
776 std::lock_guard<std::mutex> lock(mappingMutex_);
777
778 for (auto const& [_, map] : remoteMapList) {
779 // Must has valid IGD pointer and use UPNP protocol.
780 assert(map.getIgd());
781 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
782 auto& mappingList = getMappingList(map.getType());
783 auto it = mappingList.find(map.getMapKey());
784 if (it == mappingList.end()) {
785 // Not present, request mapping remove.
786 toRemoveList.emplace_back(std::move(map));
787 // Make only few remove requests at once.
788 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
789 break;
790 }
791 }
792 }
793
794 // Remove un-tracked mappings.
795 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
796 for (auto const& map : toRemoveList) {
797 protocol->requestMappingRemove(map);
798 }
799}
800
801void
802UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
803{
Adrien Béraud370257c2023-08-15 20:53:09 -0400804 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400805
806 // Use temporary list to avoid holding the lock while
807 // processing the mapping list.
808 std::list<Mapping::sharedPtr_t> toRemoveList;
809 {
810 std::lock_guard<std::mutex> lock(mappingMutex_);
811
812 PortType types[2] {PortType::TCP, PortType::UDP};
813 for (auto& type : types) {
814 auto& mappingList = getMappingList(type);
815 for (auto const& [_, map] : mappingList) {
816 if (map->getIgd() == igd)
817 toRemoveList.emplace_back(map);
818 }
819 }
820 }
821
822 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400823 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
824 map->toString(),
825 igd->toString(),
826 igd->getProtocolName());
Adrien Béraud370257c2023-08-15 20:53:09 -0400827 map->updateState(MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400828 unregisterMapping(map);
829 }
830}
831
832void
833UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
834{
835 // This list holds the mappings to be requested. This is
836 // needed to avoid performing the requests while holding
837 // the lock.
838 std::list<Mapping::sharedPtr_t> requestsList;
839
840 // Populate the list of requests to perform.
841 {
842 std::lock_guard<std::mutex> lock(mappingMutex_);
843 PortType typeArray[2] {PortType::TCP, PortType::UDP};
844
845 for (auto type : typeArray) {
846 auto& mappingList = getMappingList(type);
847 for (auto& [_, map] : mappingList) {
848 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400849 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
850 map->toString(),
851 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400852 requestsList.emplace_back(map);
853 }
854 }
855 }
856 }
857
858 // Process the pending requests.
859 for (auto const& map : requestsList) {
860 requestMapping(map);
861 }
862}
863
864void
865UPnPContext::processMappingWithAutoUpdate()
866{
867 // This list holds the mappings to be requested. This is
868 // needed to avoid performing the requests while holding
869 // the lock.
870 std::list<Mapping::sharedPtr_t> requestsList;
871
872 // Populate the list of requests for mappings with auto-update enabled.
873 {
874 std::lock_guard<std::mutex> lock(mappingMutex_);
875 PortType typeArray[2] {PortType::TCP, PortType::UDP};
876
877 for (auto type : typeArray) {
878 auto& mappingList = getMappingList(type);
879 for (auto const& [_, map] : mappingList) {
880 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
881 requestsList.emplace_back(map);
882 }
883 }
884 }
885 }
886
887 for (auto const& oldMap : requestsList) {
888 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400889 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
890 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400891
892 // Reserve a new mapping.
893 Mapping newMapping(oldMap->getType());
894 newMapping.enableAutoUpdate(true);
895 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
896
897 auto const& mapPtr = reserveMapping(newMapping);
898 assert(mapPtr);
899
900 // Release the old one.
901 oldMap->setAvailable(true);
902 oldMap->enableAutoUpdate(false);
903 oldMap->setNotifyCallback(nullptr);
904 unregisterMapping(oldMap);
905 }
906}
907
908void
909UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
910{
911 assert(igd);
912
Adrien Béraud370257c2023-08-15 20:53:09 -0400913 /*if (not isValidThread()) {
914 ctx->post([this, igd, event] { onIgdUpdated(igd, event); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400915 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400916 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400917
918 // Reset to start search for a new best IGD.
919 preferredIgd_.reset();
920
921 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
922 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
923 : "INVALID";
924
925 auto const& igdLocalAddr = igd->getLocalIp();
926 auto protocolName = igd->getProtocolName();
927
Adrien Berauda8731ac2023-08-17 12:19:39 -0400928 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
929 igd->getUID(),
930 igd->toString(),
931 protocolName,
932 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400933
934 // Check if the IGD has valid addresses.
935 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400936 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400937 return;
938 }
939
940 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400941 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400942 return;
943 }
944
945 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400946 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
947 " The mapped addresses might not be reachable",
948 protocolName,
949 igd->getPublicIp().toString(),
950 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400951 }
952
953 // The IGD was removed or is invalid.
954 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400955 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
956 igd->getUID(),
957 igd->toString(),
958 protocolName,
959 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400960
961 pruneMappingsWithInvalidIgds(igd);
962
963 std::lock_guard<std::mutex> lock(mappingMutex_);
964 validIgdList_.erase(igd);
965 return;
966 }
967
968 // Update the IGD list.
969 {
970 std::lock_guard<std::mutex> lock(mappingMutex_);
971 auto ret = validIgdList_.emplace(igd);
972 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400973 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
974 protocolName,
975 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400976 } else {
977 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400978 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
979 protocolName,
980 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400981 return;
982 }
983 }
984
985 // Update the provisionned mappings.
986 updateMappingList(false);
987}
988
989void
990UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
991{
Adrien Béraud370257c2023-08-15 20:53:09 -0400992 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400993
994 // Check if we have a pending request for this response.
995 auto map = getMappingWithKey(mapRes.getMapKey());
996 if (not map) {
997 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400998 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
999 mapRes.toString(),
1000 igd->toString(),
1001 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001002 return;
1003 }
1004
1005 // The mapping request is new and successful. Update.
1006 map->setIgd(igd);
1007 map->setInternalAddress(mapRes.getInternalAddress());
1008 map->setExternalPort(mapRes.getExternalPort());
1009
1010 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001011 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001012
Adrien Berauda8731ac2023-08-17 12:19:39 -04001013 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1014 map->toString(),
1015 igd->toString(),
1016 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001017
1018 // Call setValid() to reset the errors counter. We need
1019 // to reset the counter on each successful response.
1020 igd->setValid(true);
1021}
1022
1023#if HAVE_LIBNATPMP
1024void
1025UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1026{
1027 auto mapPtr = getMappingWithKey(map.getMapKey());
1028
1029 if (not mapPtr) {
1030 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001031 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1032 map.toString(),
1033 igd->toString(),
1034 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001035 return;
1036 }
1037 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1038 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001039 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1040 mapPtr->toString(),
1041 igd->toString(),
1042 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001043 return;
1044 }
1045
1046 mapPtr->setRenewalTime(map.getRenewalTime());
1047}
1048#endif
1049
1050void
1051UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1052{
Adrien Béraud370257c2023-08-15 20:53:09 -04001053 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001054 // Silently ignore if the mapping is invalid
1055 return;
1056 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001057 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1058 protocol->requestMappingRemove(*map);
1059}
1060
1061void
1062UPnPContext::deleteAllMappings(PortType type)
1063{
Adrien Béraud370257c2023-08-15 20:53:09 -04001064 /*if (not isValidThread()) {
1065 ctx->post([this, type] { deleteAllMappings(type); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001066 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001067 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001068
1069 std::lock_guard<std::mutex> lock(mappingMutex_);
1070 auto& mappingList = getMappingList(type);
1071
1072 for (auto const& [_, map] : mappingList) {
1073 requestRemoveMapping(map);
1074 }
1075}
1076
1077void
1078UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1079{
1080 if (not mapRes.isValid())
1081 return;
1082
Adrien Béraud370257c2023-08-15 20:53:09 -04001083 /*if (not isValidThread()) {
1084 ctx->post([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001085 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001086 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001087
1088 auto map = getMappingWithKey(mapRes.getMapKey());
1089 // Notify the listener.
1090 if (map and map->getNotifyCallback())
1091 map->getNotifyCallback()(map);
1092}
1093
1094Mapping::sharedPtr_t
1095UPnPContext::registerMapping(Mapping& map)
1096{
1097 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001098 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001099 auto port = getAvailablePortNumber(map.getType());
1100 map.setExternalPort(port);
1101 map.setInternalPort(port);
1102 }
1103
1104 // Newly added mapping must be in pending state by default.
1105 map.setState(MappingState::PENDING);
1106
1107 Mapping::sharedPtr_t mapPtr;
1108
1109 {
1110 std::lock_guard<std::mutex> lock(mappingMutex_);
1111 auto& mappingList = getMappingList(map.getType());
1112
1113 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1114 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001115 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001116 return {};
1117 }
1118 mapPtr = ret.first->second;
1119 assert(mapPtr);
1120 }
1121
1122 // No available IGD. The pending mapping requests will be processed
1123 // when a IGD becomes available (in onIgdAdded() method).
1124 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001125 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001126 } else {
1127 requestMapping(mapPtr);
1128 }
1129
1130 return mapPtr;
1131}
1132
Adrien Béraud612b55b2023-05-29 10:42:04 -04001133void
1134UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1135{
Adrien Béraud370257c2023-08-15 20:53:09 -04001136 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001137
1138 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001139 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001140 return;
1141 }
1142
1143 if (map->getAutoUpdate()) {
1144 // Dont unregister mappings with auto-update enabled.
1145 return;
1146 }
1147 auto& mappingList = getMappingList(map->getType());
1148
1149 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001150 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001151 } else {
1152 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001153 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1154 map->toString(),
1155 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001156 }
1157}
1158
1159std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1160UPnPContext::getMappingList(PortType type)
1161{
1162 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1163 return mappingList_[typeIdx];
1164}
1165
1166Mapping::sharedPtr_t
1167UPnPContext::getMappingWithKey(Mapping::key_t key)
1168{
1169 std::lock_guard<std::mutex> lock(mappingMutex_);
1170 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1171 auto it = mappingList.find(key);
1172 if (it == mappingList.end())
1173 return nullptr;
1174 return it->second;
1175}
1176
1177void
1178UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1179{
1180 std::lock_guard<std::mutex> lock(mappingMutex_);
1181 auto& mappingList = getMappingList(type);
1182
1183 for (auto const& [_, map] : mappingList) {
1184 switch (map->getState()) {
1185 case MappingState::PENDING: {
1186 status.pendingCount_++;
1187 break;
1188 }
1189 case MappingState::IN_PROGRESS: {
1190 status.inProgressCount_++;
1191 break;
1192 }
1193 case MappingState::FAILED: {
1194 status.failedCount_++;
1195 break;
1196 }
1197 case MappingState::OPEN: {
1198 status.openCount_++;
1199 if (map->isAvailable())
1200 status.readyCount_++;
1201 break;
1202 }
1203
1204 default:
1205 // Must not get here.
1206 assert(false);
1207 break;
1208 }
1209 }
1210}
1211
1212void
1213UPnPContext::getMappingStatus(MappingStatus& status)
1214{
1215 getMappingStatus(PortType::TCP, status);
1216 getMappingStatus(PortType::UDP, status);
1217}
1218
1219void
1220UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1221{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001222 auto const& map = getMappingWithKey(mapRes.getMapKey());
1223 if (not map) {
1224 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001225 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1226 mapRes.toString(),
1227 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001228 return;
1229 }
1230
1231 auto igd = map->getIgd();
1232 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001233 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001234 return;
1235 }
1236
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001237 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001238 unregisterMapping(map);
1239
Adrien Berauda8731ac2023-08-17 12:19:39 -04001240 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1241 map->toString(),
1242 igd->toString(),
1243 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001244}
1245
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001246void
1247UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1248{
1249 // CHECK_VALID_THREAD();
1250
1251 assert(map);
1252
1253 // Ignore if the state did not change.
1254 if (newState == map->getState()) {
1255 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1256 return;
1257 }
1258
1259 // Update the state.
1260 map->setState(newState);
1261
1262 // Notify the listener if set.
1263 if (notify and map->getNotifyCallback())
1264 map->getNotifyCallback()(map);
1265}
1266
Adrien Béraud612b55b2023-05-29 10:42:04 -04001267#if HAVE_LIBNATPMP
1268void
1269UPnPContext::renewAllocations()
1270{
Adrien Béraud370257c2023-08-15 20:53:09 -04001271 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001272
1273 // Check if the we have valid PMP IGD.
1274 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1275
1276 auto now = sys_clock::now();
1277 std::vector<Mapping::sharedPtr_t> toRenew;
1278
1279 for (auto type : {PortType::TCP, PortType::UDP}) {
1280 std::lock_guard<std::mutex> lock(mappingMutex_);
1281 auto mappingList = getMappingList(type);
1282 for (auto const& [_, map] : mappingList) {
1283 if (not map->isValid())
1284 continue;
1285 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1286 continue;
1287 if (map->getState() != MappingState::OPEN)
1288 continue;
1289 if (now < map->getRenewalTime())
1290 continue;
1291
1292 toRenew.emplace_back(map);
1293 }
1294 }
1295
1296 // Quit if there are no mapping to renew
1297 if (toRenew.empty())
1298 return;
1299
1300 for (auto const& map : toRenew) {
1301 pmpProto->requestMappingRenew(*map);
1302 }
1303}
1304#endif
1305
1306} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001307} // namespace dhtnet